mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-28 07:36:44 +00:00
Merge branch 'master' of https://github.com/ethereum/go-ethereum into bor
This commit is contained in:
commit
a02da31b3b
760 changed files with 31206 additions and 133336 deletions
15
.github/CODEOWNERS
vendored
15
.github/CODEOWNERS
vendored
|
|
@ -4,15 +4,20 @@
|
|||
accounts/usbwallet @karalabe
|
||||
accounts/scwallet @gballet
|
||||
accounts/abi @gballet
|
||||
cmd/clef @holiman
|
||||
cmd/puppeth @karalabe
|
||||
consensus @karalabe
|
||||
core/ @karalabe @holiman
|
||||
eth/ @karalabe
|
||||
core/ @karalabe @holiman @rjl493456442
|
||||
dashboard/ @kurkomisi
|
||||
eth/ @karalabe @holiman @rjl493456442
|
||||
graphql/ @gballet
|
||||
les/ @zsfelfoldi
|
||||
light/ @zsfelfoldi
|
||||
mobile/ @karalabe
|
||||
les/ @zsfelfoldi @rjl493456442
|
||||
light/ @zsfelfoldi @rjl493456442
|
||||
mobile/ @karalabe @ligi
|
||||
p2p/ @fjl @zsfelfoldi
|
||||
rpc/ @fjl @holiman
|
||||
p2p/simulations @zelig @nonsense @janos @justelad
|
||||
p2p/protocols @zelig @nonsense @janos @justelad
|
||||
p2p/testing @zelig @nonsense @janos @justelad
|
||||
signer/ @holiman
|
||||
whisper/ @gballet @gluk256
|
||||
|
|
|
|||
33
.travis.yml
33
.travis.yml
|
|
@ -5,35 +5,23 @@ matrix:
|
|||
include:
|
||||
- os: linux
|
||||
dist: xenial
|
||||
sudo: required
|
||||
go: 1.10.x
|
||||
script:
|
||||
- sudo modprobe fuse
|
||||
- sudo chmod 666 /dev/fuse
|
||||
- sudo chown root:$USER /etc/fuse.conf
|
||||
- go run build/ci.go install
|
||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||
|
||||
- os: linux
|
||||
dist: xenial
|
||||
sudo: required
|
||||
go: 1.11.x
|
||||
script:
|
||||
- sudo modprobe fuse
|
||||
- sudo chmod 666 /dev/fuse
|
||||
- sudo chown root:$USER /etc/fuse.conf
|
||||
- go run build/ci.go install
|
||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||
|
||||
# These are the latest Go versions.
|
||||
- os: linux
|
||||
dist: xenial
|
||||
sudo: required
|
||||
go: 1.12.x
|
||||
script:
|
||||
- sudo modprobe fuse
|
||||
- sudo chmod 666 /dev/fuse
|
||||
- sudo chown root:$USER /etc/fuse.conf
|
||||
- go run build/ci.go install
|
||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||
|
||||
|
|
@ -64,7 +52,7 @@ matrix:
|
|||
- go run build/ci.go lint
|
||||
|
||||
# This builder does the Ubuntu PPA upload
|
||||
- if: repo = ethereum/go-ethereum AND type = push
|
||||
- if: type = push
|
||||
os: linux
|
||||
dist: xenial
|
||||
go: 1.12.x
|
||||
|
|
@ -86,7 +74,7 @@ matrix:
|
|||
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||
|
||||
# This builder does the Linux Azure uploads
|
||||
- if: repo = ethereum/go-ethereum AND type = push
|
||||
- if: type = push
|
||||
os: linux
|
||||
dist: xenial
|
||||
sudo: required
|
||||
|
|
@ -120,7 +108,7 @@ matrix:
|
|||
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
|
||||
# This builder does the Linux Azure MIPS xgo uploads
|
||||
- if: repo = ethereum/go-ethereum AND type = push
|
||||
- if: type = push
|
||||
os: linux
|
||||
dist: xenial
|
||||
services:
|
||||
|
|
@ -148,7 +136,7 @@ matrix:
|
|||
- go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||
|
||||
# This builder does the Android Maven and Azure uploads
|
||||
- if: repo = ethereum/go-ethereum AND type = push
|
||||
- if: type = push
|
||||
os: linux
|
||||
dist: xenial
|
||||
addons:
|
||||
|
|
@ -185,7 +173,7 @@ matrix:
|
|||
- go run build/ci.go aar -signer ANDROID_SIGNING_KEY -deploy https://oss.sonatype.org -upload gethstore/builds
|
||||
|
||||
# This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads
|
||||
- if: repo = ethereum/go-ethereum AND type = push
|
||||
- if: type = push
|
||||
os: osx
|
||||
go: 1.12.x
|
||||
env:
|
||||
|
|
@ -214,7 +202,7 @@ matrix:
|
|||
- go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds
|
||||
|
||||
# This builder does the Azure archive purges to avoid accumulating junk
|
||||
- if: repo = ethereum/go-ethereum AND type = cron
|
||||
- if: type = cron
|
||||
os: linux
|
||||
dist: xenial
|
||||
go: 1.12.x
|
||||
|
|
@ -224,12 +212,3 @@ matrix:
|
|||
submodules: false # avoid cloning ethereum/tests
|
||||
script:
|
||||
- go run build/ci.go purge -store gethstore/builds -days 14
|
||||
|
||||
- name: Race Detector for Swarm
|
||||
if: repo = ethersphere/go-ethereum
|
||||
os: linux
|
||||
dist: xenial
|
||||
go: 1.12.x
|
||||
git:
|
||||
submodules: false # avoid cloning ethereum/tests
|
||||
script: ./build/travis_keepalive.sh go test -timeout 20m -race ./swarm... ./p2p/{protocols,simulations,testing}/...
|
||||
|
|
|
|||
10
Makefile
10
Makefile
|
|
@ -2,7 +2,7 @@
|
|||
# with Go source code. If you know what GOPATH is then you probably
|
||||
# don't need to bother with make.
|
||||
|
||||
.PHONY: geth android ios geth-cross swarm evm all test clean
|
||||
.PHONY: geth android ios geth-cross evm all test clean
|
||||
.PHONY: geth-linux geth-linux-386 geth-linux-amd64 geth-linux-mips64 geth-linux-mips64le
|
||||
.PHONY: geth-linux-arm geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64
|
||||
.PHONY: geth-darwin geth-darwin-386 geth-darwin-amd64
|
||||
|
|
@ -16,11 +16,6 @@ geth:
|
|||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
||||
|
||||
swarm:
|
||||
build/env.sh go run build/ci.go install ./cmd/swarm
|
||||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/swarm\" to launch swarm."
|
||||
|
||||
all:
|
||||
build/env.sh go run build/ci.go install
|
||||
|
||||
|
|
@ -57,9 +52,6 @@ devtools:
|
|||
@type "solc" 2> /dev/null || echo 'Please install solc'
|
||||
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
||||
|
||||
swarm-devtools:
|
||||
env GOBIN= go install ./cmd/swarm/mimegen
|
||||
|
||||
# Cross Compilation Targets (xgo)
|
||||
|
||||
geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios
|
||||
|
|
|
|||
21
README.md
21
README.md
|
|
@ -32,16 +32,15 @@ or, to build the full suite of utilities:
|
|||
|
||||
The go-ethereum project comes with several wrappers/executables found in the `cmd` directory.
|
||||
|
||||
| Command | Description |
|
||||
|:----------:|-------------|
|
||||
| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. |
|
||||
| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. |
|
||||
| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
|
||||
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
||||
| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. |
|
||||
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
|
||||
| `swarm` | Swarm daemon and tools. This is the entry point for the Swarm network. `swarm --help` for command line options and subcommands. See [Swarm README](https://github.com/ethereum/go-ethereum/tree/master/swarm) for more information. |
|
||||
| `puppeth` | a CLI wizard that aids in creating a new Ethereum network. |
|
||||
| Command | Description |
|
||||
| :-----------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **`geth`** | Our main Ethereum CLI client. It is the entry point into the Ethereum network (main-, test- or private net), capable of running as a full node (default), archive node (retaining all historical state) or a light node (retrieving data live). It can be used by other processes as a gateway into the Ethereum network via JSON RPC endpoints exposed on top of HTTP, WebSocket and/or IPC transports. `geth --help` and the [CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options) for command line options. |
|
||||
| `abigen` | Source code generator to convert Ethereum contract definitions into easy to use, compile-time type-safe Go packages. It operates on plain [Ethereum contract ABIs](https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI) with expanded functionality if the contract bytecode is also available. However, it also accepts Solidity source files, making development much more streamlined. Please see our [Native DApps](https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts) wiki page for details. |
|
||||
| `bootnode` | Stripped down version of our Ethereum client implementation that only takes part in the network node discovery protocol, but does not run any of the higher level application protocols. It can be used as a lightweight bootstrap node to aid in finding peers in private networks. |
|
||||
| `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). |
|
||||
| `gethrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. |
|
||||
| `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ethereum protocol both network as well as consensus wise) to user-friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). |
|
||||
| `puppeth` | a CLI wizard that aids in creating a new Ethereum network. |
|
||||
|
||||
## Running geth
|
||||
|
||||
|
|
@ -95,7 +94,7 @@ Specifying the `--testnet` flag, however, will reconfigure your Geth instance a
|
|||
`geth attach <datadir>/testnet/geth.ipc`. Windows users are not affected by this.
|
||||
* Instead of connecting the main Ethereum network, the client will connect to the test network,
|
||||
which uses different P2P bootnodes, different network IDs and genesis states.
|
||||
|
||||
|
||||
*Note: Although there are some internal protective measures to prevent transactions from crossing
|
||||
over between the main network and test network, you should make sure to always use separate accounts
|
||||
for play-money and real-money. Unless you manually move accounts, Geth will by default correctly
|
||||
|
|
|
|||
120
SECURITY.md
Normal file
120
SECURITY.md
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Please see Releases. We recommend to use the most recent released version.
|
||||
|
||||
## Audit reports
|
||||
|
||||
Audit reports are published in the `docs` folder: https://github.com/ethereum/go-ethereum/tree/master/docs/audits
|
||||
|
||||
|
||||
| Scope | Date | Report Link |
|
||||
| ------- | ------- | ----------- |
|
||||
| `geth` | 20170425 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2017-04-25_Geth-audit_Truesec.pdf) |
|
||||
| `clef` | 20180914 | [pdf](https://github.com/ethereum/go-ethereum/blob/master/docs/audits/2018-09-14_Clef-audit_NCC.pdf) |
|
||||
|
||||
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
**Please do not file a public ticket** mentioning the vulnerability.
|
||||
|
||||
To find out how to disclose a vulnerability in Ethereum visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email bounty@ethereum.org.
|
||||
|
||||
The following key may be used to communicate sensitive information to developers.
|
||||
|
||||
Fingerprint: `AE96 ED96 9E47 9B00 84F3 E17F E88D 3334 FA5F 6A0A`
|
||||
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Version: GnuPG v1
|
||||
|
||||
mQINBFgl3tgBEAC8A1tUBkD9YV+eLrOmtgy+/JS/H9RoZvkg3K1WZ8IYfj6iIRaY
|
||||
neAk3Bp182GUPVz/zhKr2g0tMXIScDR3EnaDsY+Qg+JqQl8NOG+Cikr1nnkG2on9
|
||||
L8c8yiqry1ZTCmYMqCa2acTFqnyuXJ482aZNtB4QG2BpzfhW4k8YThpegk/EoRUi
|
||||
m+y7buJDtoNf7YILlhDQXN8qlHB02DWOVUihph9tUIFsPK6BvTr9SIr/eG6j6k0b
|
||||
fUo9pexOn7LS4SojoJmsm/5dp6AoKlac48cZU5zwR9AYcq/nvkrfmf2WkObg/xRd
|
||||
EvKZzn05jRopmAIwmoC3CiLmqCHPmT5a29vEob/yPFE335k+ujjZCPOu7OwjzDk7
|
||||
M0zMSfnNfDq8bXh16nn+ueBxJ0NzgD1oC6c2PhM+XRQCXChoyI8vbfp4dGvCvYqv
|
||||
QAE1bWjqnumZ/7vUPgZN6gDfiAzG2mUxC2SeFBhacgzDvtQls+uuvm+FnQOUgg2H
|
||||
h8x2zgoZ7kqV29wjaUPFREuew7e+Th5BxielnzOfVycVXeSuvvIn6cd3g/s8mX1c
|
||||
2kLSXJR7+KdWDrIrR5Az0kwAqFZt6B6QTlDrPswu3mxsm5TzMbny0PsbL/HBM+GZ
|
||||
EZCjMXxB8bqV2eSaktjnSlUNX1VXxyOxXA+ZG2jwpr51egi57riVRXokrQARAQAB
|
||||
tDlFdGhlcmV1bSBGb3VuZGF0aW9uIFNlY3VyaXR5IFRlYW0gPHNlY3VyaXR5QGV0
|
||||
aGVyZXVtLm9yZz6JAj4EEwECACgCGwMGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheA
|
||||
BQJaCWH6BQkFo2BYAAoJEOiNMzT6X2oK+DEP/3H6dxkm0hvHZKoHLVuuxcu3EHYo
|
||||
k5sd3MMWPrZSN8qzZnY7ayEDMxnarWOizc+2jfOxfJlzX/g8lR1/fsHdWPFPhPoV
|
||||
Qk8ygrHn1H8U8+rpw/U03BqmqHpYCDzJ+CIis9UWROniqXw1nuqu/FtWOsdWxNKh
|
||||
jUo6k/0EsaXsxRPzgJv7fEUcVcQ7as/C3x9sy3muc2gvgA4/BKoGPb1/U0GuA8lV
|
||||
fDIDshAggmnSUAg+TuYSAAdoFQ1sKwFMPigcLJF2eyKuK3iUyixJrec/c4LSf3wA
|
||||
cGghbeuqI8INP0Y2zvXDQN2cByxsFAuoZG+m0cyKGaDH2MVUvOKKYqn/03qvrf15
|
||||
AWAsW0l0yQwOTCo3FbsNzemClm5Bj/xH0E4XuwXwChcMCMOWJrFoxyvCEI+keoQc
|
||||
c08/a8/MtS7vBAABXwOziSmm6CNqmzpWrh/fDrjlJlba9U3MxzvqU3IFlTdMratv
|
||||
6V+SgX+L25lCzW4NxxUavoB8fAlvo8lxpHKo24FP+RcLQ8XqkU3RiUsgRjQRFOqQ
|
||||
TaJcsp8mimmiYyf24mNu6b48pi+a5c/eQR9w59emeEUZqsJU+nqv8BWIIp7o4Agh
|
||||
NYnKjkhPlY5e1fLVfAHIADZFynWwRPkPMJSrBiP5EtcOFxQGHGjRxU/KjXkvE0hV
|
||||
xYb1PB8pWMTu/beeiQI+BBMBAgAoBQJYJd7YAhsDBQkB4TOABgsJCAcDAgYVCAIJ
|
||||
CgsEFgIDAQIeAQIXgAAKCRDojTM0+l9qCplDD/9IZ2i+m1cnqQKtiyHbyFGx32oL
|
||||
fzqPylX2bOG5DPsSTorSUdJMGVfT04oVxXc4S/2DVnNvi7RAbSiLapCWSplgtBOj
|
||||
j1xlblOoXxT3m7s1XHGCX5tENxI9fVSSPVKJn+fQaWpPB2MhBA+1lUI6GJ+11T7K
|
||||
J8LrP/fiw1/nOb7rW61HW44Gtyox23sA/d1+DsFVaF8hxJlNj5coPKr8xWzQ8pQl
|
||||
juzdjHDukjevuw4rRmRq9vozvj9keEU9XJ5dldyEVXFmdDk7KT0p0Rla9nxYhzf/
|
||||
r/Bv8Bzy0HCWRb2D31BjXXGG05oVnYmNGxGFxYja4MwgrMmne3ilEVjfUJsapsqi
|
||||
w41BAyQgIdfREulYN7ahsF5PrjVAqBd9IGtE8ULelF2SQxEBQBngEkP0ahP6tRAL
|
||||
i7/CBjPKOyKijtqVny7qrGOnU2ygcA88/WDibexDhrjz0Gx8WmErU7rIWZiZ5u4Y
|
||||
vJYVRo0+6rBCXRPeSJfiP5h1p17Anr2l42boAYslfcrzquB8MHtrNcyn650OLtHG
|
||||
nbxgIdniKrpuzGN6Opw+O2id2JhD1/1p4SOemwAmthplr1MIyOHNP3q93rEj2J7h
|
||||
5zPS/AJuKkMDFUpslPNLQjCOwPXtdzL7/kUZGBSyez1T3TaW1uY6l9XaJJRaSn+v
|
||||
1zPgfp4GJ3lPs4AlAbQ0RXRoZXJldW0gRm91bmRhdGlvbiBCdWcgQm91bnR5IDxi
|
||||
b3VudHlAZXRoZXJldW0ub3JnPokCPgQTAQIAKAIbAwYLCQgHAwIGFQgCCQoLBBYC
|
||||
AwECHgECF4AFAloJYfoFCQWjYFgACgkQ6I0zNPpfagoENg/+LnSaVeMxiGVtcjWl
|
||||
b7Xd73yrEy4uxiESS1AalW9mMf7oZzfI05f7QIQlaLAkNac74vZDJbPKjtb7tpMO
|
||||
RFhRZMCveq6CPKU6pd1SI8IUVUKwpEe6AJP3lHdVP57dquieFE2HlYKm6uHbCGWU
|
||||
0cjyTA+uu2KbgCHGmofsPY/xOcZLGEHTHqa5w60JJAQm+BSDKnw8wTyrxGvA3EK/
|
||||
ePSvOZMYa+iw6vYuZeBIMbdiXR/A2keBi3GuvqB8tDMj7P22TrH5mVDm3zNqGYD6
|
||||
amDPeiWp4cztY3aZyLcgYotqXPpDceZzDn+HopBPzAb/llCdE7bVswKRhphVMw4b
|
||||
bhL0R/TQY7Sf6TK2LKSBrjv0DWOSijikE71SJcBnJvHU7EpKrQQ0lMGclm3ynyji
|
||||
Nf0YTPXQt4I+fwTmOew2GFeK3UytNWbWI7oXX7Nm4bj9bhf3IJ0kmZb/Gs73+xII
|
||||
e7Rz52Mby436tWyQIQiF9ITYNGvNf53TwBBZMn0pKPiTyr3Ur7FHEotkEOFNh1//
|
||||
4zQY10XxuBdLrYGyZ4V8xHJM+oKre8Eg2R9qHXVbjvErHE+7CvgnV7YUip0criPr
|
||||
BlKRvuoJaSliH2JFhSjWVrkPmFGrWN0BAx10yIqMnEplfKeHf4P9Elek3oInS8WP
|
||||
G1zJG6s/t5+hQK0X37+TB+6rd3GJAj4EEwECACgFAlgl4TsCGwMFCQHhM4AGCwkI
|
||||
BwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEOiNMzT6X2oKzf8P/iIKd77WHTbp4pMN
|
||||
8h52HyZJtDJmjA1DPZrbGl1TesW/Z9uTd12txlgqZnbG2GfN9+LSP6EOPzR6v2xC
|
||||
OVhR+RdWhZDJJuQCVS7lJIqQrZgmeTZG0TyQPZdLjVFBOrrhVwYX+HXbu429IzHr
|
||||
URf5InyR1QgqOXyElDYS6e28HFqvaoA0DWTWDDqOLPVl+U5fuceIE2XXdv3AGLeP
|
||||
Yf8J5MPobjPiZtBqI6S6iENY2Yn35qLX+axeC/iYSCHVtFuCCIdb/QYR1ZZV8Ps/
|
||||
aI9DwC7LU+YfPw7iqCIoqxSeA3o1PORkdSigEg3jtfRv5UqVo9a0oBb9jdoADsat
|
||||
F/gW0E7mto3XGOiaR0eB9SSdsM3x7Bz4A0HIGNaxpZo1RWqlO91leP4c13Px7ISv
|
||||
5OGXfLg+M8qb+qxbGd1HpitGi9s1y1aVfEj1kOtZ0tN8eu+Upg5WKwPNBDX3ar7J
|
||||
9NCULgVSL+E79FG+zXw62gxiQrLfKzm4wU/9L5wVkwQnm29hLJ0tokrSBZFnc/1l
|
||||
7OC+GM63tYicKkY4rqmoWUeYx7IwFH9mtDtvR1RxO85RbQhZizwpZpdpRkH0DqZu
|
||||
ZJRmRa5r7rPqmfa7d+VIFhz2Xs8pJMLVqxTsLKcLglmjw7aOrYG0SWeH7YraXWGD
|
||||
N3SlvSBiVwcK7QUKzLLvpadLwxfsuQINBFgl3tgBEACbgq6HTN5gEBi0lkD/MafI
|
||||
nmNi+59U5gRGYqk46WlfRjhHudXjDpgD0lolGb4hYontkMaKRlCg2Rvgjvk3Zve0
|
||||
PKWjKw7gr8YBa9fMFY8BhAXI32OdyI9rFhxEZFfWAfwKVmT19BdeAQRFvcfd+8w8
|
||||
f1XVc+zddULMJFBTr+xKDlIRWwTkdLPQeWbjo0eHl/g4tuLiLrTxVbnj26bf+2+1
|
||||
DbM/w5VavzPrkviHqvKe/QP/gay4QDViWvFgLb90idfAHIdsPgflp0VDS5rVHFL6
|
||||
D73rSRdIRo3I8c8mYoNjSR4XDuvgOkAKW9LR3pvouFHHjp6Fr0GesRbrbb2EG66i
|
||||
PsR99MQ7FqIL9VMHPm2mtR+XvbnKkH2rYyEqaMbSdk29jGapkAWle4sIhSKk749A
|
||||
4tGkHl08KZ2N9o6GrfUehP/V2eJLaph2DioFL1HxRryrKy80QQKLMJRekxigq8gr
|
||||
eW8xB4zuf9Mkuou+RHNmo8PebHjFstLigiD6/zP2e+4tUmrT0/JTGOShoGMl8Rt0
|
||||
VRxdPImKun+4LOXbfOxArOSkY6i35+gsgkkSy1gTJE0BY3S9auT6+YrglY/TWPQ9
|
||||
IJxWVOKlT+3WIp5wJu2bBKQ420VLqDYzkoWytel/bM1ACUtipMiIVeUs2uFiRjpz
|
||||
A1Wy0QHKPTdSuGlJPRrfcQARAQABiQIlBBgBAgAPAhsMBQJaCWIIBQkFo2BYAAoJ
|
||||
EOiNMzT6X2oKgSwQAKKs7BGF8TyZeIEO2EUK7R2bdQDCdSGZY06tqLFg3IHMGxDM
|
||||
b/7FVoa2AEsFgv6xpoebxBB5zkhUk7lslgxvKiSLYjxfNjTBltfiFJ+eQnf+OTs8
|
||||
KeR51lLa66rvIH2qUzkNDCCTF45H4wIDpV05AXhBjKYkrDCrtey1rQyFp5fxI+0I
|
||||
Q1UKKXvzZK4GdxhxDbOUSd38MYy93nqcmclGSGK/gF8XiyuVjeifDCM6+T1NQTX0
|
||||
K9lneidcqtBDvlggJTLJtQPO33o5EHzXSiud+dKth1uUhZOFEaYRZoye1YE3yB0T
|
||||
NOOE8fXlvu8iuIAMBSDL9ep6sEIaXYwoD60I2gHdWD0lkP0DOjGQpi4ouXM3Edsd
|
||||
5MTi0MDRNTij431kn8T/D0LCgmoUmYYMBgbwFhXr67axPZlKjrqR0z3F/Elv0ZPP
|
||||
cVg1tNznsALYQ9Ovl6b5M3cJ5GapbbvNWC7yEE1qScl9HiMxjt/H6aPastH63/7w
|
||||
cN0TslW+zRBy05VNJvpWGStQXcngsSUeJtI1Gd992YNjUJq4/Lih6Z1TlwcFVap+
|
||||
cTcDptoUvXYGg/9mRNNPZwErSfIJ0Ibnx9wPVuRN6NiCLOt2mtKp2F1pM6AOQPpZ
|
||||
85vEh6I8i6OaO0w/Z0UHBwvpY6jDUliaROsWUQsqz78Z34CVj4cy6vPW2EF4
|
||||
=r6KK
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
|
@ -42,6 +43,24 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
|
|||
return NewKeyedTransactor(key.PrivateKey), nil
|
||||
}
|
||||
|
||||
// NewTransactor is a utility method to easily create a transaction signer from
|
||||
// an decrypted key from a keystore
|
||||
func NewTransactorFromKeyStore(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
|
||||
return &TransactOpts{
|
||||
From: account.Address,
|
||||
Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||
if address != account.Address {
|
||||
return nil, errors.New("not authorized to sign this account")
|
||||
}
|
||||
signature, err := keystore.SignHash(account, signer.Hash(tx).Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tx.WithSignature(signer, signature)
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewKeyedTransactor is a utility method to easily create a transaction signer
|
||||
// from a single private key.
|
||||
func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build darwin,!ios freebsd linux,!arm64 netbsd solaris
|
||||
// +build darwin,!ios,cgo freebsd linux,!arm64 netbsd solaris
|
||||
|
||||
package keystore
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
|
||||
// +build darwin,!cgo ios linux,arm64 windows !darwin,!freebsd,!linux,!netbsd,!solaris
|
||||
|
||||
// This is the fallback implementation of directory watching.
|
||||
// It is used on unsupported platforms.
|
||||
|
|
|
|||
|
|
@ -25,13 +25,13 @@
|
|||
Start `geth` with the `console` command. You will notice the following warning:
|
||||
|
||||
```
|
||||
WARN [04-09|16:58:38.898] Failed to open wallet url=pcsc://044def09 err="smartcard: pairing password needed"
|
||||
WARN [04-09|16:58:38.898] Failed to open wallet url=keycard://044def09 err="smartcard: pairing password needed"
|
||||
```
|
||||
|
||||
Write down the URL (`pcsc://044def09` in this example). Then ask `geth` to open the wallet:
|
||||
Write down the URL (`keycard://044def09` in this example). Then ask `geth` to open the wallet:
|
||||
|
||||
```
|
||||
> personal.openWallet("pcsc://044def09")
|
||||
> personal.openWallet("keycard://044def09")
|
||||
Please enter the pairing password:
|
||||
```
|
||||
|
||||
|
|
@ -42,12 +42,12 @@
|
|||
|
||||
```
|
||||
> personal
|
||||
WARN [04-09|17:02:07.330] Smartcard wallet account derivation failed url=pcsc://044def09 err="Unexpected response status Cla=0x80, Ins=0xd1, Sw=0x6985"
|
||||
WARN [04-09|17:02:07.330] Smartcard wallet account derivation failed url=keycard://044def09 err="Unexpected response status Cla=0x80, Ins=0xd1, Sw=0x6985"
|
||||
{
|
||||
listAccounts: [],
|
||||
listWallets: [{
|
||||
status: "Empty, waiting for initialization",
|
||||
url: "pcsc://044def09"
|
||||
url: "keycard://044def09"
|
||||
}],
|
||||
...
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
So the communication with the card is working, but there is no key associated with this wallet. Let's create it:
|
||||
|
||||
```
|
||||
> personal.initializeWallet("pcsc://044def09")
|
||||
> personal.initializeWallet("keycard://044def09")
|
||||
"tilt ... impact"
|
||||
```
|
||||
|
||||
|
|
@ -67,10 +67,10 @@
|
|||
[{
|
||||
accounts: [{
|
||||
address: "0x678b7cd55c61917defb23546a41803c5bfefbc7a",
|
||||
url: "pcsc://044d/m/44'/60'/0'/0/0"
|
||||
url: "keycard://044d/m/44'/60'/0'/0/0"
|
||||
}],
|
||||
status: "Online",
|
||||
url: "pcsc://044def09"
|
||||
url: "keycard://044def09"
|
||||
}]
|
||||
```
|
||||
|
||||
|
|
@ -84,14 +84,14 @@
|
|||
```
|
||||
listWallets: [{
|
||||
status: "Online, can derive public keys",
|
||||
url: "pcsc://a4d73015"
|
||||
url: "keycard://a4d73015"
|
||||
}]
|
||||
```
|
||||
|
||||
3. Open the wallet, you will be prompted for your pairing password, then PIN:
|
||||
|
||||
```
|
||||
personal.openWallet("pcsc://a4d73015")
|
||||
personal.openWallet("keycard://a4d73015")
|
||||
```
|
||||
|
||||
4. Check that creation was successful by typing e.g. `personal`. Then use it like a regular wallet.
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ import (
|
|||
)
|
||||
|
||||
// Scheme is the URI prefix for smartcard wallets.
|
||||
const Scheme = "pcsc"
|
||||
const Scheme = "keycard"
|
||||
|
||||
// refreshCycle is the maximum time between wallet refreshes (if USB hotplug
|
||||
// notifications don't work).
|
||||
|
|
@ -152,8 +152,8 @@ func (hub *Hub) setPairing(wallet *Wallet, pairing *smartcardPairing) error {
|
|||
}
|
||||
|
||||
// NewHub creates a new hardware wallet manager for smartcards.
|
||||
func NewHub(scheme string, datadir string) (*Hub, error) {
|
||||
context, err := pcsc.EstablishContext(pcsc.ScopeSystem)
|
||||
func NewHub(daemonPath string, scheme string, datadir string) (*Hub, error) {
|
||||
context, err := pcsc.EstablishContext(daemonPath, pcsc.ScopeSystem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -37,7 +38,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
pcsc "github.com/gballet/go-libpcsclite"
|
||||
"github.com/status-im/keycard-go/derivationpath"
|
||||
|
|
@ -311,8 +311,10 @@ func (w *Wallet) Status() (string, error) {
|
|||
return fmt.Sprintf("Failed: %v", err), err
|
||||
}
|
||||
switch {
|
||||
case !w.session.verified && status.PinRetryCount == 0 && status.PukRetryCount == 0:
|
||||
return fmt.Sprintf("Bricked, waiting for full wipe"), nil
|
||||
case !w.session.verified && status.PinRetryCount == 0:
|
||||
return fmt.Sprintf("Blocked, waiting for PUK and new PIN"), nil
|
||||
return fmt.Sprintf("Blocked, waiting for PUK (%d attempts left) and new PIN", status.PukRetryCount), nil
|
||||
case !w.session.verified:
|
||||
return fmt.Sprintf("Locked, waiting for PIN (%d attempts left)", status.PinRetryCount), nil
|
||||
case !status.Initialized:
|
||||
|
|
@ -378,10 +380,18 @@ func (w *Wallet) Open(passphrase string) error {
|
|||
case passphrase == "":
|
||||
return ErrPINUnblockNeeded
|
||||
case status.PinRetryCount > 0:
|
||||
if !regexp.MustCompile(`^[0-9]{6,}$`).MatchString(passphrase) {
|
||||
w.log.Error("PIN needs to be at least 6 digits")
|
||||
return ErrPINNeeded
|
||||
}
|
||||
if err := w.session.verifyPin([]byte(passphrase)); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
if !regexp.MustCompile(`^[0-9]{12,}$`).MatchString(passphrase) {
|
||||
w.log.Error("PUK needs to be at least 12 digits")
|
||||
return ErrPINUnblockNeeded
|
||||
}
|
||||
if err := w.session.unblockPin([]byte(passphrase)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -972,12 +982,10 @@ func (s *Session) derive(path accounts.DerivationPath) (accounts.Account, error)
|
|||
copy(sig[32-len(rbytes):32], rbytes)
|
||||
copy(sig[64-len(sbytes):64], sbytes)
|
||||
|
||||
pubkey, err := determinePublicKey(sig, sigdata.PublicKey)
|
||||
if err != nil {
|
||||
if err := confirmPublicKey(sig, sigdata.PublicKey); err != nil {
|
||||
return accounts.Account{}, err
|
||||
}
|
||||
|
||||
pub, err := crypto.UnmarshalPubkey(pubkey)
|
||||
pub, err := crypto.UnmarshalPubkey(sigdata.PublicKey)
|
||||
if err != nil {
|
||||
return accounts.Account{}, err
|
||||
}
|
||||
|
|
@ -1047,36 +1055,28 @@ func (s *Session) sign(path accounts.DerivationPath, hash []byte) ([]byte, error
|
|||
return sig, nil
|
||||
}
|
||||
|
||||
// determinePublicKey uses a signature and the X component of a public key to
|
||||
// recover the entire public key.
|
||||
func determinePublicKey(sig, pubkeyX []byte) ([]byte, error) {
|
||||
for v := 0; v < 2; v++ {
|
||||
sig[64] = byte(v)
|
||||
pubkey, err := crypto.Ecrecover(DerivationSignatureHash[:], sig)
|
||||
if err == nil {
|
||||
if bytes.Equal(pubkey, pubkeyX) {
|
||||
return pubkey, nil
|
||||
}
|
||||
} else if v == 1 || err != secp256k1.ErrRecoverFailed {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, ErrPubkeyMismatch
|
||||
// confirmPublicKey confirms that the given signature belongs to the specified key.
|
||||
func confirmPublicKey(sig, pubkey []byte) error {
|
||||
_, err := makeRecoverableSignature(DerivationSignatureHash[:], sig, pubkey)
|
||||
return err
|
||||
}
|
||||
|
||||
// makeRecoverableSignature uses a signature and an expected public key to
|
||||
// recover the v value and produce a recoverable signature.
|
||||
func makeRecoverableSignature(hash, sig, expectedPubkey []byte) ([]byte, error) {
|
||||
var libraryError error
|
||||
for v := 0; v < 2; v++ {
|
||||
sig[64] = byte(v)
|
||||
pubkey, err := crypto.Ecrecover(hash, sig)
|
||||
if err == nil {
|
||||
if pubkey, err := crypto.Ecrecover(hash, sig); err == nil {
|
||||
if bytes.Equal(pubkey, expectedPubkey) {
|
||||
return sig, nil
|
||||
}
|
||||
} else if v == 1 || err != secp256k1.ErrRecoverFailed {
|
||||
return nil, err
|
||||
} else {
|
||||
libraryError = err
|
||||
}
|
||||
}
|
||||
if libraryError != nil {
|
||||
return nil, libraryError
|
||||
}
|
||||
return nil, ErrPubkeyMismatch
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@ import (
|
|||
"errors"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/karalabe/hid"
|
||||
"github.com/karalabe/usb"
|
||||
)
|
||||
|
||||
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
||||
|
|
@ -64,21 +65,41 @@ type Hub struct {
|
|||
// TODO(karalabe): remove if hotplug lands on Windows
|
||||
commsPend int // Number of operations blocking enumeration
|
||||
commsLock sync.Mutex // Lock protecting the pending counter and enumeration
|
||||
enumFails uint32 // Number of times enumeration has failed
|
||||
}
|
||||
|
||||
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
||||
func NewLedgerHub() (*Hub, error) {
|
||||
return newHub(LedgerScheme, 0x2c97, []uint16{0x0000 /* Ledger Blue */, 0x0001 /* Ledger Nano S */}, 0xffa0, 0, newLedgerDriver)
|
||||
return newHub(LedgerScheme, 0x2c97, []uint16{
|
||||
// Original product IDs
|
||||
0x0000, /* Ledger Blue */
|
||||
0x0001, /* Ledger Nano S */
|
||||
0x0004, /* Ledger Nano X */
|
||||
|
||||
// Upcoming product IDs: https://www.ledger.com/2019/05/17/windows-10-update-sunsetting-u2f-tunnel-transport-for-ledger-devices/
|
||||
0x0015, /* HID + U2F + WebUSB Ledger Blue */
|
||||
0x1015, /* HID + U2F + WebUSB Ledger Nano S */
|
||||
0x4015, /* HID + U2F + WebUSB Ledger Nano X */
|
||||
0x0011, /* HID + WebUSB Ledger Blue */
|
||||
0x1011, /* HID + WebUSB Ledger Nano S */
|
||||
0x4011, /* HID + WebUSB Ledger Nano X */
|
||||
}, 0xffa0, 0, newLedgerDriver)
|
||||
}
|
||||
|
||||
// NewTrezorHub creates a new hardware wallet manager for Trezor devices.
|
||||
func NewTrezorHub() (*Hub, error) {
|
||||
return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor 1 */}, 0xff00, 0, newTrezorDriver)
|
||||
// NewTrezorHubWithHID creates a new hardware wallet manager for Trezor devices.
|
||||
func NewTrezorHubWithHID() (*Hub, error) {
|
||||
return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor HID */}, 0xff00, 0, newTrezorDriver)
|
||||
}
|
||||
|
||||
// NewTrezorHubWithWebUSB creates a new hardware wallet manager for Trezor devices with
|
||||
// firmware version > 1.8.0
|
||||
func NewTrezorHubWithWebUSB() (*Hub, error) {
|
||||
return newHub(TrezorScheme, 0x1209, []uint16{0x53c1 /* Trezor WebUSB */}, 0xffff /* No usage id on webusb, don't match unset (0) */, 0, newTrezorDriver)
|
||||
}
|
||||
|
||||
// newHub creates a new hardware wallet manager for generic USB devices.
|
||||
func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) {
|
||||
if !hid.Supported() {
|
||||
if !usb.Supported() {
|
||||
return nil, errors.New("unsupported platform")
|
||||
}
|
||||
hub := &Hub{
|
||||
|
|
@ -119,8 +140,12 @@ func (hub *Hub) refreshWallets() {
|
|||
if elapsed < refreshThrottling {
|
||||
return
|
||||
}
|
||||
// If USB enumeration is continually failing, don't keep trying indefinitely
|
||||
if atomic.LoadUint32(&hub.enumFails) > 2 {
|
||||
return
|
||||
}
|
||||
// Retrieve the current list of USB wallet devices
|
||||
var devices []hid.DeviceInfo
|
||||
var devices []usb.DeviceInfo
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
// hidapi on Linux opens the device during enumeration to retrieve some infos,
|
||||
|
|
@ -135,8 +160,22 @@ func (hub *Hub) refreshWallets() {
|
|||
return
|
||||
}
|
||||
}
|
||||
for _, info := range hid.Enumerate(hub.vendorID, 0) {
|
||||
infos, err := usb.Enumerate(hub.vendorID, 0)
|
||||
if err != nil {
|
||||
failcount := atomic.AddUint32(&hub.enumFails, 1)
|
||||
if runtime.GOOS == "linux" {
|
||||
// See rationale before the enumeration why this is needed and only on Linux.
|
||||
hub.commsLock.Unlock()
|
||||
}
|
||||
log.Error("Failed to enumerate USB devices", "hub", hub.scheme,
|
||||
"vendor", hub.vendorID, "failcount", failcount, "err", err)
|
||||
return
|
||||
}
|
||||
atomic.StoreUint32(&hub.enumFails, 0)
|
||||
|
||||
for _, info := range infos {
|
||||
for _, id := range hub.productIDs {
|
||||
// Windows and Macos use UsageID matching, Linux uses Interface matching
|
||||
if info.ProductID == id && (info.UsagePage == hub.usageID || info.Interface == hub.endpointID) {
|
||||
devices = append(devices, info)
|
||||
break
|
||||
|
|
|
|||
|
|
@ -192,7 +192,13 @@ func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, er
|
|||
if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
return common.BytesToAddress(address.GetAddress()), nil
|
||||
if addr := address.GetAddressBin(); len(addr) > 0 { // Older firmwares use binary fomats
|
||||
return common.BytesToAddress(addr), nil
|
||||
}
|
||||
if addr := address.GetAddressHex(); len(addr) > 0 { // Newer firmwares use hexadecimal fomats
|
||||
return common.HexToAddress(addr), nil
|
||||
}
|
||||
return common.Address{}, errors.New("missing derived address")
|
||||
}
|
||||
|
||||
// trezorSign sends the transaction to the Trezor wallet, and waits for the user
|
||||
|
|
@ -211,7 +217,10 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
|
|||
DataLength: &length,
|
||||
}
|
||||
if to := tx.To(); to != nil {
|
||||
request.To = (*to)[:] // Non contract deploy, set recipient explicitly
|
||||
// Non contract deploy, set recipient explicitly
|
||||
hex := to.Hex()
|
||||
request.ToHex = &hex // Newer firmwares (old will ignore)
|
||||
request.ToBin = (*to)[:] // Older firmwares (new will ignore)
|
||||
}
|
||||
if length > 1024 { // Send the data chunked if that was requested
|
||||
request.DataInitialChunk, data = data[:1024], data[1024:]
|
||||
|
|
|
|||
811
accounts/usbwallet/trezor/messages-common.pb.go
Normal file
811
accounts/usbwallet/trezor/messages-common.pb.go
Normal file
|
|
@ -0,0 +1,811 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: messages-common.proto
|
||||
|
||||
package trezor
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
math "math"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
type Failure_FailureType int32
|
||||
|
||||
const (
|
||||
Failure_Failure_UnexpectedMessage Failure_FailureType = 1
|
||||
Failure_Failure_ButtonExpected Failure_FailureType = 2
|
||||
Failure_Failure_DataError Failure_FailureType = 3
|
||||
Failure_Failure_ActionCancelled Failure_FailureType = 4
|
||||
Failure_Failure_PinExpected Failure_FailureType = 5
|
||||
Failure_Failure_PinCancelled Failure_FailureType = 6
|
||||
Failure_Failure_PinInvalid Failure_FailureType = 7
|
||||
Failure_Failure_InvalidSignature Failure_FailureType = 8
|
||||
Failure_Failure_ProcessError Failure_FailureType = 9
|
||||
Failure_Failure_NotEnoughFunds Failure_FailureType = 10
|
||||
Failure_Failure_NotInitialized Failure_FailureType = 11
|
||||
Failure_Failure_PinMismatch Failure_FailureType = 12
|
||||
Failure_Failure_FirmwareError Failure_FailureType = 99
|
||||
)
|
||||
|
||||
var Failure_FailureType_name = map[int32]string{
|
||||
1: "Failure_UnexpectedMessage",
|
||||
2: "Failure_ButtonExpected",
|
||||
3: "Failure_DataError",
|
||||
4: "Failure_ActionCancelled",
|
||||
5: "Failure_PinExpected",
|
||||
6: "Failure_PinCancelled",
|
||||
7: "Failure_PinInvalid",
|
||||
8: "Failure_InvalidSignature",
|
||||
9: "Failure_ProcessError",
|
||||
10: "Failure_NotEnoughFunds",
|
||||
11: "Failure_NotInitialized",
|
||||
12: "Failure_PinMismatch",
|
||||
99: "Failure_FirmwareError",
|
||||
}
|
||||
|
||||
var Failure_FailureType_value = map[string]int32{
|
||||
"Failure_UnexpectedMessage": 1,
|
||||
"Failure_ButtonExpected": 2,
|
||||
"Failure_DataError": 3,
|
||||
"Failure_ActionCancelled": 4,
|
||||
"Failure_PinExpected": 5,
|
||||
"Failure_PinCancelled": 6,
|
||||
"Failure_PinInvalid": 7,
|
||||
"Failure_InvalidSignature": 8,
|
||||
"Failure_ProcessError": 9,
|
||||
"Failure_NotEnoughFunds": 10,
|
||||
"Failure_NotInitialized": 11,
|
||||
"Failure_PinMismatch": 12,
|
||||
"Failure_FirmwareError": 99,
|
||||
}
|
||||
|
||||
func (x Failure_FailureType) Enum() *Failure_FailureType {
|
||||
p := new(Failure_FailureType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Failure_FailureType) String() string {
|
||||
return proto.EnumName(Failure_FailureType_name, int32(x))
|
||||
}
|
||||
|
||||
func (x *Failure_FailureType) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(Failure_FailureType_value, data, "Failure_FailureType")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = Failure_FailureType(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (Failure_FailureType) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{1, 0}
|
||||
}
|
||||
|
||||
//*
|
||||
// Type of button request
|
||||
type ButtonRequest_ButtonRequestType int32
|
||||
|
||||
const (
|
||||
ButtonRequest_ButtonRequest_Other ButtonRequest_ButtonRequestType = 1
|
||||
ButtonRequest_ButtonRequest_FeeOverThreshold ButtonRequest_ButtonRequestType = 2
|
||||
ButtonRequest_ButtonRequest_ConfirmOutput ButtonRequest_ButtonRequestType = 3
|
||||
ButtonRequest_ButtonRequest_ResetDevice ButtonRequest_ButtonRequestType = 4
|
||||
ButtonRequest_ButtonRequest_ConfirmWord ButtonRequest_ButtonRequestType = 5
|
||||
ButtonRequest_ButtonRequest_WipeDevice ButtonRequest_ButtonRequestType = 6
|
||||
ButtonRequest_ButtonRequest_ProtectCall ButtonRequest_ButtonRequestType = 7
|
||||
ButtonRequest_ButtonRequest_SignTx ButtonRequest_ButtonRequestType = 8
|
||||
ButtonRequest_ButtonRequest_FirmwareCheck ButtonRequest_ButtonRequestType = 9
|
||||
ButtonRequest_ButtonRequest_Address ButtonRequest_ButtonRequestType = 10
|
||||
ButtonRequest_ButtonRequest_PublicKey ButtonRequest_ButtonRequestType = 11
|
||||
ButtonRequest_ButtonRequest_MnemonicWordCount ButtonRequest_ButtonRequestType = 12
|
||||
ButtonRequest_ButtonRequest_MnemonicInput ButtonRequest_ButtonRequestType = 13
|
||||
ButtonRequest_ButtonRequest_PassphraseType ButtonRequest_ButtonRequestType = 14
|
||||
ButtonRequest_ButtonRequest_UnknownDerivationPath ButtonRequest_ButtonRequestType = 15
|
||||
)
|
||||
|
||||
var ButtonRequest_ButtonRequestType_name = map[int32]string{
|
||||
1: "ButtonRequest_Other",
|
||||
2: "ButtonRequest_FeeOverThreshold",
|
||||
3: "ButtonRequest_ConfirmOutput",
|
||||
4: "ButtonRequest_ResetDevice",
|
||||
5: "ButtonRequest_ConfirmWord",
|
||||
6: "ButtonRequest_WipeDevice",
|
||||
7: "ButtonRequest_ProtectCall",
|
||||
8: "ButtonRequest_SignTx",
|
||||
9: "ButtonRequest_FirmwareCheck",
|
||||
10: "ButtonRequest_Address",
|
||||
11: "ButtonRequest_PublicKey",
|
||||
12: "ButtonRequest_MnemonicWordCount",
|
||||
13: "ButtonRequest_MnemonicInput",
|
||||
14: "ButtonRequest_PassphraseType",
|
||||
15: "ButtonRequest_UnknownDerivationPath",
|
||||
}
|
||||
|
||||
var ButtonRequest_ButtonRequestType_value = map[string]int32{
|
||||
"ButtonRequest_Other": 1,
|
||||
"ButtonRequest_FeeOverThreshold": 2,
|
||||
"ButtonRequest_ConfirmOutput": 3,
|
||||
"ButtonRequest_ResetDevice": 4,
|
||||
"ButtonRequest_ConfirmWord": 5,
|
||||
"ButtonRequest_WipeDevice": 6,
|
||||
"ButtonRequest_ProtectCall": 7,
|
||||
"ButtonRequest_SignTx": 8,
|
||||
"ButtonRequest_FirmwareCheck": 9,
|
||||
"ButtonRequest_Address": 10,
|
||||
"ButtonRequest_PublicKey": 11,
|
||||
"ButtonRequest_MnemonicWordCount": 12,
|
||||
"ButtonRequest_MnemonicInput": 13,
|
||||
"ButtonRequest_PassphraseType": 14,
|
||||
"ButtonRequest_UnknownDerivationPath": 15,
|
||||
}
|
||||
|
||||
func (x ButtonRequest_ButtonRequestType) Enum() *ButtonRequest_ButtonRequestType {
|
||||
p := new(ButtonRequest_ButtonRequestType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x ButtonRequest_ButtonRequestType) String() string {
|
||||
return proto.EnumName(ButtonRequest_ButtonRequestType_name, int32(x))
|
||||
}
|
||||
|
||||
func (x *ButtonRequest_ButtonRequestType) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(ButtonRequest_ButtonRequestType_value, data, "ButtonRequest_ButtonRequestType")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = ButtonRequest_ButtonRequestType(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ButtonRequest_ButtonRequestType) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{2, 0}
|
||||
}
|
||||
|
||||
//*
|
||||
// Type of PIN request
|
||||
type PinMatrixRequest_PinMatrixRequestType int32
|
||||
|
||||
const (
|
||||
PinMatrixRequest_PinMatrixRequestType_Current PinMatrixRequest_PinMatrixRequestType = 1
|
||||
PinMatrixRequest_PinMatrixRequestType_NewFirst PinMatrixRequest_PinMatrixRequestType = 2
|
||||
PinMatrixRequest_PinMatrixRequestType_NewSecond PinMatrixRequest_PinMatrixRequestType = 3
|
||||
)
|
||||
|
||||
var PinMatrixRequest_PinMatrixRequestType_name = map[int32]string{
|
||||
1: "PinMatrixRequestType_Current",
|
||||
2: "PinMatrixRequestType_NewFirst",
|
||||
3: "PinMatrixRequestType_NewSecond",
|
||||
}
|
||||
|
||||
var PinMatrixRequest_PinMatrixRequestType_value = map[string]int32{
|
||||
"PinMatrixRequestType_Current": 1,
|
||||
"PinMatrixRequestType_NewFirst": 2,
|
||||
"PinMatrixRequestType_NewSecond": 3,
|
||||
}
|
||||
|
||||
func (x PinMatrixRequest_PinMatrixRequestType) Enum() *PinMatrixRequest_PinMatrixRequestType {
|
||||
p := new(PinMatrixRequest_PinMatrixRequestType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x PinMatrixRequest_PinMatrixRequestType) String() string {
|
||||
return proto.EnumName(PinMatrixRequest_PinMatrixRequestType_name, int32(x))
|
||||
}
|
||||
|
||||
func (x *PinMatrixRequest_PinMatrixRequestType) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(PinMatrixRequest_PinMatrixRequestType_value, data, "PinMatrixRequest_PinMatrixRequestType")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = PinMatrixRequest_PinMatrixRequestType(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (PinMatrixRequest_PinMatrixRequestType) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{4, 0}
|
||||
}
|
||||
|
||||
//*
|
||||
// Response: Success of the previous request
|
||||
// @end
|
||||
type Success struct {
|
||||
Message *string `protobuf:"bytes,1,opt,name=message" json:"message,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Success) Reset() { *m = Success{} }
|
||||
func (m *Success) String() string { return proto.CompactTextString(m) }
|
||||
func (*Success) ProtoMessage() {}
|
||||
func (*Success) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{0}
|
||||
}
|
||||
|
||||
func (m *Success) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Success.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Success) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Success.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Success) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Success.Merge(m, src)
|
||||
}
|
||||
func (m *Success) XXX_Size() int {
|
||||
return xxx_messageInfo_Success.Size(m)
|
||||
}
|
||||
func (m *Success) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Success.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Success proto.InternalMessageInfo
|
||||
|
||||
func (m *Success) GetMessage() string {
|
||||
if m != nil && m.Message != nil {
|
||||
return *m.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//*
|
||||
// Response: Failure of the previous request
|
||||
// @end
|
||||
type Failure struct {
|
||||
Code *Failure_FailureType `protobuf:"varint,1,opt,name=code,enum=hw.trezor.messages.common.Failure_FailureType" json:"code,omitempty"`
|
||||
Message *string `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Failure) Reset() { *m = Failure{} }
|
||||
func (m *Failure) String() string { return proto.CompactTextString(m) }
|
||||
func (*Failure) ProtoMessage() {}
|
||||
func (*Failure) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{1}
|
||||
}
|
||||
|
||||
func (m *Failure) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_Failure.Unmarshal(m, b)
|
||||
}
|
||||
func (m *Failure) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_Failure.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *Failure) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Failure.Merge(m, src)
|
||||
}
|
||||
func (m *Failure) XXX_Size() int {
|
||||
return xxx_messageInfo_Failure.Size(m)
|
||||
}
|
||||
func (m *Failure) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Failure.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Failure proto.InternalMessageInfo
|
||||
|
||||
func (m *Failure) GetCode() Failure_FailureType {
|
||||
if m != nil && m.Code != nil {
|
||||
return *m.Code
|
||||
}
|
||||
return Failure_Failure_UnexpectedMessage
|
||||
}
|
||||
|
||||
func (m *Failure) GetMessage() string {
|
||||
if m != nil && m.Message != nil {
|
||||
return *m.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//*
|
||||
// Response: Device is waiting for HW button press.
|
||||
// @auxstart
|
||||
// @next ButtonAck
|
||||
type ButtonRequest struct {
|
||||
Code *ButtonRequest_ButtonRequestType `protobuf:"varint,1,opt,name=code,enum=hw.trezor.messages.common.ButtonRequest_ButtonRequestType" json:"code,omitempty"`
|
||||
Data *string `protobuf:"bytes,2,opt,name=data" json:"data,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ButtonRequest) Reset() { *m = ButtonRequest{} }
|
||||
func (m *ButtonRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*ButtonRequest) ProtoMessage() {}
|
||||
func (*ButtonRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{2}
|
||||
}
|
||||
|
||||
func (m *ButtonRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ButtonRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ButtonRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ButtonRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ButtonRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ButtonRequest.Merge(m, src)
|
||||
}
|
||||
func (m *ButtonRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_ButtonRequest.Size(m)
|
||||
}
|
||||
func (m *ButtonRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ButtonRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ButtonRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *ButtonRequest) GetCode() ButtonRequest_ButtonRequestType {
|
||||
if m != nil && m.Code != nil {
|
||||
return *m.Code
|
||||
}
|
||||
return ButtonRequest_ButtonRequest_Other
|
||||
}
|
||||
|
||||
func (m *ButtonRequest) GetData() string {
|
||||
if m != nil && m.Data != nil {
|
||||
return *m.Data
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//*
|
||||
// Request: Computer agrees to wait for HW button press
|
||||
// @auxend
|
||||
type ButtonAck struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *ButtonAck) Reset() { *m = ButtonAck{} }
|
||||
func (m *ButtonAck) String() string { return proto.CompactTextString(m) }
|
||||
func (*ButtonAck) ProtoMessage() {}
|
||||
func (*ButtonAck) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{3}
|
||||
}
|
||||
|
||||
func (m *ButtonAck) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_ButtonAck.Unmarshal(m, b)
|
||||
}
|
||||
func (m *ButtonAck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_ButtonAck.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *ButtonAck) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_ButtonAck.Merge(m, src)
|
||||
}
|
||||
func (m *ButtonAck) XXX_Size() int {
|
||||
return xxx_messageInfo_ButtonAck.Size(m)
|
||||
}
|
||||
func (m *ButtonAck) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_ButtonAck.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_ButtonAck proto.InternalMessageInfo
|
||||
|
||||
//*
|
||||
// Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme
|
||||
// @auxstart
|
||||
// @next PinMatrixAck
|
||||
type PinMatrixRequest struct {
|
||||
Type *PinMatrixRequest_PinMatrixRequestType `protobuf:"varint,1,opt,name=type,enum=hw.trezor.messages.common.PinMatrixRequest_PinMatrixRequestType" json:"type,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *PinMatrixRequest) Reset() { *m = PinMatrixRequest{} }
|
||||
func (m *PinMatrixRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*PinMatrixRequest) ProtoMessage() {}
|
||||
func (*PinMatrixRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{4}
|
||||
}
|
||||
|
||||
func (m *PinMatrixRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_PinMatrixRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *PinMatrixRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_PinMatrixRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *PinMatrixRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PinMatrixRequest.Merge(m, src)
|
||||
}
|
||||
func (m *PinMatrixRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_PinMatrixRequest.Size(m)
|
||||
}
|
||||
func (m *PinMatrixRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PinMatrixRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PinMatrixRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *PinMatrixRequest) GetType() PinMatrixRequest_PinMatrixRequestType {
|
||||
if m != nil && m.Type != nil {
|
||||
return *m.Type
|
||||
}
|
||||
return PinMatrixRequest_PinMatrixRequestType_Current
|
||||
}
|
||||
|
||||
//*
|
||||
// Request: Computer responds with encoded PIN
|
||||
// @auxend
|
||||
type PinMatrixAck struct {
|
||||
Pin *string `protobuf:"bytes,1,req,name=pin" json:"pin,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *PinMatrixAck) Reset() { *m = PinMatrixAck{} }
|
||||
func (m *PinMatrixAck) String() string { return proto.CompactTextString(m) }
|
||||
func (*PinMatrixAck) ProtoMessage() {}
|
||||
func (*PinMatrixAck) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{5}
|
||||
}
|
||||
|
||||
func (m *PinMatrixAck) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_PinMatrixAck.Unmarshal(m, b)
|
||||
}
|
||||
func (m *PinMatrixAck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_PinMatrixAck.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *PinMatrixAck) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PinMatrixAck.Merge(m, src)
|
||||
}
|
||||
func (m *PinMatrixAck) XXX_Size() int {
|
||||
return xxx_messageInfo_PinMatrixAck.Size(m)
|
||||
}
|
||||
func (m *PinMatrixAck) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PinMatrixAck.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PinMatrixAck proto.InternalMessageInfo
|
||||
|
||||
func (m *PinMatrixAck) GetPin() string {
|
||||
if m != nil && m.Pin != nil {
|
||||
return *m.Pin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//*
|
||||
// Response: Device awaits encryption passphrase
|
||||
// @auxstart
|
||||
// @next PassphraseAck
|
||||
type PassphraseRequest struct {
|
||||
OnDevice *bool `protobuf:"varint,1,opt,name=on_device,json=onDevice" json:"on_device,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *PassphraseRequest) Reset() { *m = PassphraseRequest{} }
|
||||
func (m *PassphraseRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*PassphraseRequest) ProtoMessage() {}
|
||||
func (*PassphraseRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{6}
|
||||
}
|
||||
|
||||
func (m *PassphraseRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_PassphraseRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *PassphraseRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_PassphraseRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *PassphraseRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PassphraseRequest.Merge(m, src)
|
||||
}
|
||||
func (m *PassphraseRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_PassphraseRequest.Size(m)
|
||||
}
|
||||
func (m *PassphraseRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PassphraseRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PassphraseRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *PassphraseRequest) GetOnDevice() bool {
|
||||
if m != nil && m.OnDevice != nil {
|
||||
return *m.OnDevice
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//*
|
||||
// Request: Send passphrase back
|
||||
// @next PassphraseStateRequest
|
||||
type PassphraseAck struct {
|
||||
Passphrase *string `protobuf:"bytes,1,opt,name=passphrase" json:"passphrase,omitempty"`
|
||||
State []byte `protobuf:"bytes,2,opt,name=state" json:"state,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *PassphraseAck) Reset() { *m = PassphraseAck{} }
|
||||
func (m *PassphraseAck) String() string { return proto.CompactTextString(m) }
|
||||
func (*PassphraseAck) ProtoMessage() {}
|
||||
func (*PassphraseAck) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{7}
|
||||
}
|
||||
|
||||
func (m *PassphraseAck) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_PassphraseAck.Unmarshal(m, b)
|
||||
}
|
||||
func (m *PassphraseAck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_PassphraseAck.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *PassphraseAck) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PassphraseAck.Merge(m, src)
|
||||
}
|
||||
func (m *PassphraseAck) XXX_Size() int {
|
||||
return xxx_messageInfo_PassphraseAck.Size(m)
|
||||
}
|
||||
func (m *PassphraseAck) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PassphraseAck.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PassphraseAck proto.InternalMessageInfo
|
||||
|
||||
func (m *PassphraseAck) GetPassphrase() string {
|
||||
if m != nil && m.Passphrase != nil {
|
||||
return *m.Passphrase
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *PassphraseAck) GetState() []byte {
|
||||
if m != nil {
|
||||
return m.State
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//*
|
||||
// Response: Device awaits passphrase state
|
||||
// @next PassphraseStateAck
|
||||
type PassphraseStateRequest struct {
|
||||
State []byte `protobuf:"bytes,1,opt,name=state" json:"state,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *PassphraseStateRequest) Reset() { *m = PassphraseStateRequest{} }
|
||||
func (m *PassphraseStateRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*PassphraseStateRequest) ProtoMessage() {}
|
||||
func (*PassphraseStateRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{8}
|
||||
}
|
||||
|
||||
func (m *PassphraseStateRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_PassphraseStateRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *PassphraseStateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_PassphraseStateRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *PassphraseStateRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PassphraseStateRequest.Merge(m, src)
|
||||
}
|
||||
func (m *PassphraseStateRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_PassphraseStateRequest.Size(m)
|
||||
}
|
||||
func (m *PassphraseStateRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PassphraseStateRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PassphraseStateRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *PassphraseStateRequest) GetState() []byte {
|
||||
if m != nil {
|
||||
return m.State
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//*
|
||||
// Request: Send passphrase state back
|
||||
// @auxend
|
||||
type PassphraseStateAck struct {
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *PassphraseStateAck) Reset() { *m = PassphraseStateAck{} }
|
||||
func (m *PassphraseStateAck) String() string { return proto.CompactTextString(m) }
|
||||
func (*PassphraseStateAck) ProtoMessage() {}
|
||||
func (*PassphraseStateAck) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{9}
|
||||
}
|
||||
|
||||
func (m *PassphraseStateAck) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_PassphraseStateAck.Unmarshal(m, b)
|
||||
}
|
||||
func (m *PassphraseStateAck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_PassphraseStateAck.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *PassphraseStateAck) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_PassphraseStateAck.Merge(m, src)
|
||||
}
|
||||
func (m *PassphraseStateAck) XXX_Size() int {
|
||||
return xxx_messageInfo_PassphraseStateAck.Size(m)
|
||||
}
|
||||
func (m *PassphraseStateAck) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_PassphraseStateAck.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_PassphraseStateAck proto.InternalMessageInfo
|
||||
|
||||
//*
|
||||
// Structure representing BIP32 (hierarchical deterministic) node
|
||||
// Used for imports of private key into the device and exporting public key out of device
|
||||
// @embed
|
||||
type HDNodeType struct {
|
||||
Depth *uint32 `protobuf:"varint,1,req,name=depth" json:"depth,omitempty"`
|
||||
Fingerprint *uint32 `protobuf:"varint,2,req,name=fingerprint" json:"fingerprint,omitempty"`
|
||||
ChildNum *uint32 `protobuf:"varint,3,req,name=child_num,json=childNum" json:"child_num,omitempty"`
|
||||
ChainCode []byte `protobuf:"bytes,4,req,name=chain_code,json=chainCode" json:"chain_code,omitempty"`
|
||||
PrivateKey []byte `protobuf:"bytes,5,opt,name=private_key,json=privateKey" json:"private_key,omitempty"`
|
||||
PublicKey []byte `protobuf:"bytes,6,opt,name=public_key,json=publicKey" json:"public_key,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *HDNodeType) Reset() { *m = HDNodeType{} }
|
||||
func (m *HDNodeType) String() string { return proto.CompactTextString(m) }
|
||||
func (*HDNodeType) ProtoMessage() {}
|
||||
func (*HDNodeType) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaf30d059fdbc38d, []int{10}
|
||||
}
|
||||
|
||||
func (m *HDNodeType) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_HDNodeType.Unmarshal(m, b)
|
||||
}
|
||||
func (m *HDNodeType) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_HDNodeType.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *HDNodeType) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_HDNodeType.Merge(m, src)
|
||||
}
|
||||
func (m *HDNodeType) XXX_Size() int {
|
||||
return xxx_messageInfo_HDNodeType.Size(m)
|
||||
}
|
||||
func (m *HDNodeType) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_HDNodeType.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_HDNodeType proto.InternalMessageInfo
|
||||
|
||||
func (m *HDNodeType) GetDepth() uint32 {
|
||||
if m != nil && m.Depth != nil {
|
||||
return *m.Depth
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *HDNodeType) GetFingerprint() uint32 {
|
||||
if m != nil && m.Fingerprint != nil {
|
||||
return *m.Fingerprint
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *HDNodeType) GetChildNum() uint32 {
|
||||
if m != nil && m.ChildNum != nil {
|
||||
return *m.ChildNum
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *HDNodeType) GetChainCode() []byte {
|
||||
if m != nil {
|
||||
return m.ChainCode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *HDNodeType) GetPrivateKey() []byte {
|
||||
if m != nil {
|
||||
return m.PrivateKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *HDNodeType) GetPublicKey() []byte {
|
||||
if m != nil {
|
||||
return m.PublicKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("hw.trezor.messages.common.Failure_FailureType", Failure_FailureType_name, Failure_FailureType_value)
|
||||
proto.RegisterEnum("hw.trezor.messages.common.ButtonRequest_ButtonRequestType", ButtonRequest_ButtonRequestType_name, ButtonRequest_ButtonRequestType_value)
|
||||
proto.RegisterEnum("hw.trezor.messages.common.PinMatrixRequest_PinMatrixRequestType", PinMatrixRequest_PinMatrixRequestType_name, PinMatrixRequest_PinMatrixRequestType_value)
|
||||
proto.RegisterType((*Success)(nil), "hw.trezor.messages.common.Success")
|
||||
proto.RegisterType((*Failure)(nil), "hw.trezor.messages.common.Failure")
|
||||
proto.RegisterType((*ButtonRequest)(nil), "hw.trezor.messages.common.ButtonRequest")
|
||||
proto.RegisterType((*ButtonAck)(nil), "hw.trezor.messages.common.ButtonAck")
|
||||
proto.RegisterType((*PinMatrixRequest)(nil), "hw.trezor.messages.common.PinMatrixRequest")
|
||||
proto.RegisterType((*PinMatrixAck)(nil), "hw.trezor.messages.common.PinMatrixAck")
|
||||
proto.RegisterType((*PassphraseRequest)(nil), "hw.trezor.messages.common.PassphraseRequest")
|
||||
proto.RegisterType((*PassphraseAck)(nil), "hw.trezor.messages.common.PassphraseAck")
|
||||
proto.RegisterType((*PassphraseStateRequest)(nil), "hw.trezor.messages.common.PassphraseStateRequest")
|
||||
proto.RegisterType((*PassphraseStateAck)(nil), "hw.trezor.messages.common.PassphraseStateAck")
|
||||
proto.RegisterType((*HDNodeType)(nil), "hw.trezor.messages.common.HDNodeType")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("messages-common.proto", fileDescriptor_aaf30d059fdbc38d) }
|
||||
|
||||
var fileDescriptor_aaf30d059fdbc38d = []byte{
|
||||
// 846 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xcd, 0x52, 0x23, 0x37,
|
||||
0x10, 0x2e, 0xff, 0x80, 0xed, 0xb6, 0xd9, 0x08, 0xc5, 0x80, 0x09, 0xb0, 0x38, 0xc3, 0x21, 0x5c,
|
||||
0xe2, 0x4a, 0xe5, 0x98, 0x53, 0x58, 0x83, 0x2b, 0xd4, 0x16, 0x86, 0x1a, 0xd8, 0xda, 0xa3, 0x4b,
|
||||
0xd1, 0xf4, 0x32, 0x2a, 0xcf, 0x48, 0x13, 0x8d, 0x06, 0xf0, 0x5e, 0xf2, 0x6a, 0x79, 0x89, 0xbc,
|
||||
0x42, 0xaa, 0x52, 0xb9, 0xe4, 0x11, 0xb6, 0x34, 0x3f, 0x78, 0xc6, 0x66, 0x39, 0xcd, 0xe8, 0xfb,
|
||||
0xbe, 0xee, 0x96, 0xba, 0x3f, 0x09, 0x76, 0x42, 0x8c, 0x63, 0x76, 0x8f, 0xf1, 0x8f, 0x5c, 0x85,
|
||||
0xa1, 0x92, 0xa3, 0x48, 0x2b, 0xa3, 0xe8, 0xbe, 0xff, 0x38, 0x32, 0x1a, 0x3f, 0x2b, 0x3d, 0x2a,
|
||||
0x04, 0xa3, 0x4c, 0xe0, 0x9c, 0x40, 0xeb, 0x36, 0xe1, 0x1c, 0xe3, 0x98, 0x0e, 0xa0, 0x95, 0xb3,
|
||||
0x83, 0xda, 0xb0, 0x76, 0xda, 0x71, 0x8b, 0xa5, 0xf3, 0x77, 0x03, 0x5a, 0x13, 0x26, 0x82, 0x44,
|
||||
0x23, 0x7d, 0x07, 0x4d, 0xae, 0xbc, 0x4c, 0xf2, 0xe6, 0xe7, 0xd1, 0xe8, 0xab, 0xa9, 0x47, 0x79,
|
||||
0x44, 0xf1, 0xbd, 0x5b, 0x44, 0xe8, 0xa6, 0xb1, 0xe5, 0x4a, 0xf5, 0x6a, 0xa5, 0xff, 0xea, 0xd0,
|
||||
0x2d, 0xe9, 0xe9, 0x11, 0xec, 0xe7, 0xcb, 0xd9, 0x07, 0x89, 0x4f, 0x11, 0x72, 0x83, 0xde, 0x55,
|
||||
0x26, 0x26, 0x35, 0xfa, 0x1d, 0xec, 0x16, 0xf4, 0xbb, 0xc4, 0x18, 0x25, 0x2f, 0x72, 0x09, 0xa9,
|
||||
0xd3, 0x1d, 0xd8, 0x2e, 0xb8, 0x73, 0x66, 0xd8, 0x85, 0xd6, 0x4a, 0x93, 0x06, 0x3d, 0x80, 0xbd,
|
||||
0x02, 0x3e, 0xe3, 0x46, 0x28, 0x39, 0x66, 0x92, 0x63, 0x10, 0xa0, 0x47, 0x9a, 0x74, 0x0f, 0xbe,
|
||||
0x2d, 0xc8, 0x1b, 0xb1, 0x4c, 0xb6, 0x41, 0x07, 0xd0, 0x2f, 0x11, 0xcb, 0x90, 0x4d, 0xba, 0x0b,
|
||||
0xb4, 0xc4, 0x5c, 0xca, 0x07, 0x16, 0x08, 0x8f, 0xb4, 0xe8, 0x21, 0x0c, 0x0a, 0x3c, 0x07, 0x6f,
|
||||
0xc5, 0xbd, 0x64, 0x26, 0xd1, 0x48, 0xda, 0x95, 0x7c, 0x5a, 0xd9, 0xf6, 0x67, 0xfb, 0xeb, 0x94,
|
||||
0x8f, 0x34, 0x55, 0xe6, 0x42, 0xaa, 0xe4, 0xde, 0x9f, 0x24, 0xd2, 0x8b, 0x09, 0xac, 0x70, 0x97,
|
||||
0x52, 0x18, 0xc1, 0x02, 0xf1, 0x19, 0x3d, 0xd2, 0x5d, 0xd9, 0xfa, 0x95, 0x88, 0x43, 0x66, 0xb8,
|
||||
0x4f, 0x7a, 0x74, 0x1f, 0x76, 0x0a, 0x62, 0x22, 0x74, 0xf8, 0xc8, 0x34, 0x66, 0xb5, 0xb8, 0xf3,
|
||||
0x4f, 0x13, 0xb6, 0xb2, 0xbe, 0xb9, 0xf8, 0x47, 0x82, 0xb1, 0xa1, 0xd3, 0xca, 0x74, 0x7f, 0x79,
|
||||
0x65, 0xba, 0x95, 0xb8, 0xea, 0xaa, 0x34, 0x69, 0x0a, 0x4d, 0x8f, 0x19, 0x96, 0x8f, 0x39, 0xfd,
|
||||
0x77, 0xfe, 0x6f, 0xc0, 0xf6, 0x9a, 0xde, 0xee, 0xbf, 0x02, 0xce, 0xae, 0x8d, 0x8f, 0x9a, 0xd4,
|
||||
0xa8, 0x03, 0x6f, 0xab, 0xc4, 0x04, 0xf1, 0xfa, 0x01, 0xf5, 0x9d, 0xaf, 0x31, 0xf6, 0x55, 0x60,
|
||||
0x67, 0x7d, 0x0c, 0x07, 0x55, 0xcd, 0x58, 0xc9, 0x4f, 0x42, 0x87, 0xd7, 0x89, 0x89, 0x12, 0x43,
|
||||
0x1a, 0xd6, 0x47, 0x55, 0x81, 0x8b, 0x31, 0x9a, 0x73, 0x7c, 0x10, 0x1c, 0x49, 0x73, 0x9d, 0xce,
|
||||
0xe3, 0x3f, 0x2a, 0x6d, 0xa7, 0x7f, 0x08, 0x83, 0x2a, 0xfd, 0x51, 0x44, 0x98, 0x07, 0x6f, 0xae,
|
||||
0x07, 0xdf, 0x68, 0x65, 0x90, 0x9b, 0x31, 0x0b, 0x02, 0xd2, 0xb2, 0xa3, 0xae, 0xd2, 0xd6, 0x07,
|
||||
0x77, 0x4f, 0xa4, 0xbd, 0xbe, 0xeb, 0x62, 0x3e, 0x63, 0x1f, 0xf9, 0x9c, 0x74, 0xec, 0xe8, 0xaa,
|
||||
0x82, 0x33, 0xcf, 0xd3, 0x18, 0x5b, 0x2b, 0x1c, 0xc0, 0xde, 0x4a, 0xd1, 0xe4, 0xf7, 0x40, 0xf0,
|
||||
0xf7, 0xb8, 0x20, 0x5d, 0x7a, 0x02, 0xc7, 0x55, 0xf2, 0x4a, 0x62, 0xa8, 0xa4, 0xe0, 0xf6, 0x3c,
|
||||
0x63, 0x95, 0x48, 0x43, 0x7a, 0xeb, 0xd5, 0x0b, 0xd1, 0xa5, 0xb4, 0x3d, 0xdb, 0xa2, 0x43, 0x38,
|
||||
0x5c, 0x29, 0xc1, 0xe2, 0x38, 0xf2, 0x35, 0x8b, 0xd3, 0xbb, 0x49, 0xde, 0xd0, 0x1f, 0xe0, 0xa4,
|
||||
0xaa, 0xf8, 0x20, 0xe7, 0x52, 0x3d, 0xca, 0x73, 0xd4, 0xe2, 0x81, 0xd9, 0xcb, 0x75, 0xc3, 0x8c,
|
||||
0x4f, 0xbe, 0x71, 0xba, 0xd0, 0xc9, 0x84, 0x67, 0x7c, 0xee, 0xfc, 0x5b, 0x03, 0x62, 0x2d, 0xca,
|
||||
0x8c, 0x16, 0x4f, 0x85, 0xf1, 0xee, 0xa0, 0x69, 0x16, 0x51, 0x61, 0xbc, 0x5f, 0x5f, 0x31, 0xde,
|
||||
0x6a, 0xe8, 0x1a, 0x90, 0xd9, 0xcf, 0x66, 0x73, 0xfe, 0x84, 0xfe, 0x4b, 0xac, 0x3d, 0xda, 0x4b,
|
||||
0xf8, 0x6c, 0x9c, 0x68, 0x8d, 0xd2, 0x90, 0x1a, 0xfd, 0x1e, 0x8e, 0x5e, 0x54, 0x4c, 0xf1, 0x71,
|
||||
0x22, 0x74, 0x6c, 0x48, 0xdd, 0x1a, 0xf3, 0x6b, 0x92, 0x5b, 0xe4, 0x4a, 0x7a, 0xa4, 0xe1, 0x0c,
|
||||
0xa1, 0xf7, 0xac, 0x39, 0xe3, 0x73, 0x4a, 0xa0, 0x11, 0x09, 0x39, 0xa8, 0x0d, 0xeb, 0xa7, 0x1d,
|
||||
0xd7, 0xfe, 0x3a, 0x3f, 0xc1, 0xf6, 0xb2, 0xaf, 0x45, 0x37, 0x0e, 0xa0, 0xa3, 0xe4, 0xcc, 0x4b,
|
||||
0x1d, 0x96, 0xb6, 0xa4, 0xed, 0xb6, 0x95, 0xcc, 0x1c, 0xe7, 0x5c, 0xc0, 0xd6, 0x32, 0xc2, 0x26,
|
||||
0x7d, 0x0b, 0x10, 0x3d, 0x03, 0xf9, 0xdb, 0x5d, 0x42, 0x68, 0x1f, 0x36, 0x62, 0xc3, 0x4c, 0xf6,
|
||||
0xd8, 0xf6, 0xdc, 0x6c, 0xe1, 0x8c, 0x60, 0x77, 0x99, 0xe6, 0xd6, 0x42, 0x45, 0xf5, 0x67, 0x7d,
|
||||
0xad, 0xac, 0xef, 0x03, 0x5d, 0xd1, 0xdb, 0x61, 0xfe, 0x55, 0x03, 0xf8, 0xed, 0x7c, 0xaa, 0xbc,
|
||||
0xec, 0xbd, 0xee, 0xc3, 0x86, 0x87, 0x91, 0xf1, 0xd3, 0x13, 0x6e, 0xb9, 0xd9, 0x82, 0x0e, 0xa1,
|
||||
0xfb, 0x49, 0xc8, 0x7b, 0xd4, 0x91, 0x16, 0xd2, 0x0c, 0xea, 0x29, 0x57, 0x86, 0xec, 0x81, 0xb9,
|
||||
0x2f, 0x02, 0x6f, 0x26, 0x93, 0x70, 0xd0, 0x48, 0xf9, 0x76, 0x0a, 0x4c, 0x93, 0x90, 0x1e, 0x01,
|
||||
0x70, 0x9f, 0x09, 0x39, 0x4b, 0x9f, 0xa6, 0xe6, 0xb0, 0x7e, 0xda, 0x73, 0x3b, 0x29, 0x32, 0xb6,
|
||||
0x6f, 0xcc, 0x31, 0x74, 0xa3, 0xd4, 0x6f, 0x38, 0x9b, 0xe3, 0x62, 0xb0, 0x91, 0x6e, 0x1a, 0x72,
|
||||
0xe8, 0x3d, 0x2e, 0x6c, 0x7c, 0x94, 0xde, 0x8e, 0x94, 0xdf, 0x4c, 0xf9, 0x4e, 0x54, 0xdc, 0x97,
|
||||
0x2f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xb2, 0x7d, 0x20, 0xa6, 0x35, 0x07, 0x00, 0x00,
|
||||
}
|
||||
147
accounts/usbwallet/trezor/messages-common.proto
Normal file
147
accounts/usbwallet/trezor/messages-common.proto
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||
// https://github.com/trezor/trezor-common/blob/master/protob/messages-common.proto
|
||||
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9.
|
||||
|
||||
syntax = "proto2";
|
||||
package hw.trezor.messages.common;
|
||||
|
||||
/**
|
||||
* Response: Success of the previous request
|
||||
* @end
|
||||
*/
|
||||
message Success {
|
||||
optional string message = 1; // human readable description of action or request-specific payload
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Failure of the previous request
|
||||
* @end
|
||||
*/
|
||||
message Failure {
|
||||
optional FailureType code = 1; // computer-readable definition of the error state
|
||||
optional string message = 2; // human-readable message of the error state
|
||||
enum FailureType {
|
||||
Failure_UnexpectedMessage = 1;
|
||||
Failure_ButtonExpected = 2;
|
||||
Failure_DataError = 3;
|
||||
Failure_ActionCancelled = 4;
|
||||
Failure_PinExpected = 5;
|
||||
Failure_PinCancelled = 6;
|
||||
Failure_PinInvalid = 7;
|
||||
Failure_InvalidSignature = 8;
|
||||
Failure_ProcessError = 9;
|
||||
Failure_NotEnoughFunds = 10;
|
||||
Failure_NotInitialized = 11;
|
||||
Failure_PinMismatch = 12;
|
||||
Failure_FirmwareError = 99;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device is waiting for HW button press.
|
||||
* @auxstart
|
||||
* @next ButtonAck
|
||||
*/
|
||||
message ButtonRequest {
|
||||
optional ButtonRequestType code = 1;
|
||||
optional string data = 2;
|
||||
/**
|
||||
* Type of button request
|
||||
*/
|
||||
enum ButtonRequestType {
|
||||
ButtonRequest_Other = 1;
|
||||
ButtonRequest_FeeOverThreshold = 2;
|
||||
ButtonRequest_ConfirmOutput = 3;
|
||||
ButtonRequest_ResetDevice = 4;
|
||||
ButtonRequest_ConfirmWord = 5;
|
||||
ButtonRequest_WipeDevice = 6;
|
||||
ButtonRequest_ProtectCall = 7;
|
||||
ButtonRequest_SignTx = 8;
|
||||
ButtonRequest_FirmwareCheck = 9;
|
||||
ButtonRequest_Address = 10;
|
||||
ButtonRequest_PublicKey = 11;
|
||||
ButtonRequest_MnemonicWordCount = 12;
|
||||
ButtonRequest_MnemonicInput = 13;
|
||||
ButtonRequest_PassphraseType = 14;
|
||||
ButtonRequest_UnknownDerivationPath = 15;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Computer agrees to wait for HW button press
|
||||
* @auxend
|
||||
*/
|
||||
message ButtonAck {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme
|
||||
* @auxstart
|
||||
* @next PinMatrixAck
|
||||
*/
|
||||
message PinMatrixRequest {
|
||||
optional PinMatrixRequestType type = 1;
|
||||
/**
|
||||
* Type of PIN request
|
||||
*/
|
||||
enum PinMatrixRequestType {
|
||||
PinMatrixRequestType_Current = 1;
|
||||
PinMatrixRequestType_NewFirst = 2;
|
||||
PinMatrixRequestType_NewSecond = 3;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Computer responds with encoded PIN
|
||||
* @auxend
|
||||
*/
|
||||
message PinMatrixAck {
|
||||
required string pin = 1; // matrix encoded PIN entered by user
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device awaits encryption passphrase
|
||||
* @auxstart
|
||||
* @next PassphraseAck
|
||||
*/
|
||||
message PassphraseRequest {
|
||||
optional bool on_device = 1; // passphrase is being entered on the device
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Send passphrase back
|
||||
* @next PassphraseStateRequest
|
||||
*/
|
||||
message PassphraseAck {
|
||||
optional string passphrase = 1;
|
||||
optional bytes state = 2; // expected device state
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device awaits passphrase state
|
||||
* @next PassphraseStateAck
|
||||
*/
|
||||
message PassphraseStateRequest {
|
||||
optional bytes state = 1; // actual device state
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Send passphrase state back
|
||||
* @auxend
|
||||
*/
|
||||
message PassphraseStateAck {
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing BIP32 (hierarchical deterministic) node
|
||||
* Used for imports of private key into the device and exporting public key out of device
|
||||
* @embed
|
||||
*/
|
||||
message HDNodeType {
|
||||
required uint32 depth = 1;
|
||||
required uint32 fingerprint = 2;
|
||||
required uint32 child_num = 3;
|
||||
required bytes chain_code = 4;
|
||||
optional bytes private_key = 5;
|
||||
optional bytes public_key = 6;
|
||||
}
|
||||
698
accounts/usbwallet/trezor/messages-ethereum.pb.go
Normal file
698
accounts/usbwallet/trezor/messages-ethereum.pb.go
Normal file
|
|
@ -0,0 +1,698 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: messages-ethereum.proto
|
||||
|
||||
package trezor
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
math "math"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
//*
|
||||
// Request: Ask device for public key corresponding to address_n path
|
||||
// @start
|
||||
// @next EthereumPublicKey
|
||||
// @next Failure
|
||||
type EthereumGetPublicKey struct {
|
||||
AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"`
|
||||
ShowDisplay *bool `protobuf:"varint,2,opt,name=show_display,json=showDisplay" json:"show_display,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EthereumGetPublicKey) Reset() { *m = EthereumGetPublicKey{} }
|
||||
func (m *EthereumGetPublicKey) String() string { return proto.CompactTextString(m) }
|
||||
func (*EthereumGetPublicKey) ProtoMessage() {}
|
||||
func (*EthereumGetPublicKey) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_cb33f46ba915f15c, []int{0}
|
||||
}
|
||||
|
||||
func (m *EthereumGetPublicKey) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EthereumGetPublicKey.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EthereumGetPublicKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EthereumGetPublicKey.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EthereumGetPublicKey) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EthereumGetPublicKey.Merge(m, src)
|
||||
}
|
||||
func (m *EthereumGetPublicKey) XXX_Size() int {
|
||||
return xxx_messageInfo_EthereumGetPublicKey.Size(m)
|
||||
}
|
||||
func (m *EthereumGetPublicKey) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EthereumGetPublicKey.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EthereumGetPublicKey proto.InternalMessageInfo
|
||||
|
||||
func (m *EthereumGetPublicKey) GetAddressN() []uint32 {
|
||||
if m != nil {
|
||||
return m.AddressN
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumGetPublicKey) GetShowDisplay() bool {
|
||||
if m != nil && m.ShowDisplay != nil {
|
||||
return *m.ShowDisplay
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//*
|
||||
// Response: Contains public key derived from device private seed
|
||||
// @end
|
||||
type EthereumPublicKey struct {
|
||||
Node *HDNodeType `protobuf:"bytes,1,opt,name=node" json:"node,omitempty"`
|
||||
Xpub *string `protobuf:"bytes,2,opt,name=xpub" json:"xpub,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EthereumPublicKey) Reset() { *m = EthereumPublicKey{} }
|
||||
func (m *EthereumPublicKey) String() string { return proto.CompactTextString(m) }
|
||||
func (*EthereumPublicKey) ProtoMessage() {}
|
||||
func (*EthereumPublicKey) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_cb33f46ba915f15c, []int{1}
|
||||
}
|
||||
|
||||
func (m *EthereumPublicKey) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EthereumPublicKey.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EthereumPublicKey) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EthereumPublicKey.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EthereumPublicKey) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EthereumPublicKey.Merge(m, src)
|
||||
}
|
||||
func (m *EthereumPublicKey) XXX_Size() int {
|
||||
return xxx_messageInfo_EthereumPublicKey.Size(m)
|
||||
}
|
||||
func (m *EthereumPublicKey) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EthereumPublicKey.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EthereumPublicKey proto.InternalMessageInfo
|
||||
|
||||
func (m *EthereumPublicKey) GetNode() *HDNodeType {
|
||||
if m != nil {
|
||||
return m.Node
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumPublicKey) GetXpub() string {
|
||||
if m != nil && m.Xpub != nil {
|
||||
return *m.Xpub
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//*
|
||||
// Request: Ask device for Ethereum address corresponding to address_n path
|
||||
// @start
|
||||
// @next EthereumAddress
|
||||
// @next Failure
|
||||
type EthereumGetAddress struct {
|
||||
AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"`
|
||||
ShowDisplay *bool `protobuf:"varint,2,opt,name=show_display,json=showDisplay" json:"show_display,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EthereumGetAddress) Reset() { *m = EthereumGetAddress{} }
|
||||
func (m *EthereumGetAddress) String() string { return proto.CompactTextString(m) }
|
||||
func (*EthereumGetAddress) ProtoMessage() {}
|
||||
func (*EthereumGetAddress) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_cb33f46ba915f15c, []int{2}
|
||||
}
|
||||
|
||||
func (m *EthereumGetAddress) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EthereumGetAddress.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EthereumGetAddress) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EthereumGetAddress.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EthereumGetAddress) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EthereumGetAddress.Merge(m, src)
|
||||
}
|
||||
func (m *EthereumGetAddress) XXX_Size() int {
|
||||
return xxx_messageInfo_EthereumGetAddress.Size(m)
|
||||
}
|
||||
func (m *EthereumGetAddress) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EthereumGetAddress.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EthereumGetAddress proto.InternalMessageInfo
|
||||
|
||||
func (m *EthereumGetAddress) GetAddressN() []uint32 {
|
||||
if m != nil {
|
||||
return m.AddressN
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumGetAddress) GetShowDisplay() bool {
|
||||
if m != nil && m.ShowDisplay != nil {
|
||||
return *m.ShowDisplay
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
//*
|
||||
// Response: Contains an Ethereum address derived from device private seed
|
||||
// @end
|
||||
type EthereumAddress struct {
|
||||
AddressBin []byte `protobuf:"bytes,1,opt,name=addressBin" json:"addressBin,omitempty"`
|
||||
AddressHex *string `protobuf:"bytes,2,opt,name=addressHex" json:"addressHex,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EthereumAddress) Reset() { *m = EthereumAddress{} }
|
||||
func (m *EthereumAddress) String() string { return proto.CompactTextString(m) }
|
||||
func (*EthereumAddress) ProtoMessage() {}
|
||||
func (*EthereumAddress) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_cb33f46ba915f15c, []int{3}
|
||||
}
|
||||
|
||||
func (m *EthereumAddress) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EthereumAddress.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EthereumAddress) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EthereumAddress.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EthereumAddress) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EthereumAddress.Merge(m, src)
|
||||
}
|
||||
func (m *EthereumAddress) XXX_Size() int {
|
||||
return xxx_messageInfo_EthereumAddress.Size(m)
|
||||
}
|
||||
func (m *EthereumAddress) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EthereumAddress.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EthereumAddress proto.InternalMessageInfo
|
||||
|
||||
func (m *EthereumAddress) GetAddressBin() []byte {
|
||||
if m != nil {
|
||||
return m.AddressBin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumAddress) GetAddressHex() string {
|
||||
if m != nil && m.AddressHex != nil {
|
||||
return *m.AddressHex
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//*
|
||||
// Request: Ask device to sign transaction
|
||||
// All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing.
|
||||
// Note: the first at most 1024 bytes of data MUST be transmitted as part of this message.
|
||||
// @start
|
||||
// @next EthereumTxRequest
|
||||
// @next Failure
|
||||
type EthereumSignTx struct {
|
||||
AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"`
|
||||
Nonce []byte `protobuf:"bytes,2,opt,name=nonce" json:"nonce,omitempty"`
|
||||
GasPrice []byte `protobuf:"bytes,3,opt,name=gas_price,json=gasPrice" json:"gas_price,omitempty"`
|
||||
GasLimit []byte `protobuf:"bytes,4,opt,name=gas_limit,json=gasLimit" json:"gas_limit,omitempty"`
|
||||
ToBin []byte `protobuf:"bytes,5,opt,name=toBin" json:"toBin,omitempty"`
|
||||
ToHex *string `protobuf:"bytes,11,opt,name=toHex" json:"toHex,omitempty"`
|
||||
Value []byte `protobuf:"bytes,6,opt,name=value" json:"value,omitempty"`
|
||||
DataInitialChunk []byte `protobuf:"bytes,7,opt,name=data_initial_chunk,json=dataInitialChunk" json:"data_initial_chunk,omitempty"`
|
||||
DataLength *uint32 `protobuf:"varint,8,opt,name=data_length,json=dataLength" json:"data_length,omitempty"`
|
||||
ChainId *uint32 `protobuf:"varint,9,opt,name=chain_id,json=chainId" json:"chain_id,omitempty"`
|
||||
TxType *uint32 `protobuf:"varint,10,opt,name=tx_type,json=txType" json:"tx_type,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) Reset() { *m = EthereumSignTx{} }
|
||||
func (m *EthereumSignTx) String() string { return proto.CompactTextString(m) }
|
||||
func (*EthereumSignTx) ProtoMessage() {}
|
||||
func (*EthereumSignTx) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_cb33f46ba915f15c, []int{4}
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EthereumSignTx.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EthereumSignTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EthereumSignTx.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EthereumSignTx) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EthereumSignTx.Merge(m, src)
|
||||
}
|
||||
func (m *EthereumSignTx) XXX_Size() int {
|
||||
return xxx_messageInfo_EthereumSignTx.Size(m)
|
||||
}
|
||||
func (m *EthereumSignTx) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EthereumSignTx.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EthereumSignTx proto.InternalMessageInfo
|
||||
|
||||
func (m *EthereumSignTx) GetAddressN() []uint32 {
|
||||
if m != nil {
|
||||
return m.AddressN
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) GetNonce() []byte {
|
||||
if m != nil {
|
||||
return m.Nonce
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) GetGasPrice() []byte {
|
||||
if m != nil {
|
||||
return m.GasPrice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) GetGasLimit() []byte {
|
||||
if m != nil {
|
||||
return m.GasLimit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) GetToBin() []byte {
|
||||
if m != nil {
|
||||
return m.ToBin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) GetToHex() string {
|
||||
if m != nil && m.ToHex != nil {
|
||||
return *m.ToHex
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) GetValue() []byte {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) GetDataInitialChunk() []byte {
|
||||
if m != nil {
|
||||
return m.DataInitialChunk
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) GetDataLength() uint32 {
|
||||
if m != nil && m.DataLength != nil {
|
||||
return *m.DataLength
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) GetChainId() uint32 {
|
||||
if m != nil && m.ChainId != nil {
|
||||
return *m.ChainId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *EthereumSignTx) GetTxType() uint32 {
|
||||
if m != nil && m.TxType != nil {
|
||||
return *m.TxType
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
//*
|
||||
// Response: Device asks for more data from transaction payload, or returns the signature.
|
||||
// If data_length is set, device awaits that many more bytes of payload.
|
||||
// Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present.
|
||||
// @end
|
||||
// @next EthereumTxAck
|
||||
type EthereumTxRequest struct {
|
||||
DataLength *uint32 `protobuf:"varint,1,opt,name=data_length,json=dataLength" json:"data_length,omitempty"`
|
||||
SignatureV *uint32 `protobuf:"varint,2,opt,name=signature_v,json=signatureV" json:"signature_v,omitempty"`
|
||||
SignatureR []byte `protobuf:"bytes,3,opt,name=signature_r,json=signatureR" json:"signature_r,omitempty"`
|
||||
SignatureS []byte `protobuf:"bytes,4,opt,name=signature_s,json=signatureS" json:"signature_s,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EthereumTxRequest) Reset() { *m = EthereumTxRequest{} }
|
||||
func (m *EthereumTxRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*EthereumTxRequest) ProtoMessage() {}
|
||||
func (*EthereumTxRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_cb33f46ba915f15c, []int{5}
|
||||
}
|
||||
|
||||
func (m *EthereumTxRequest) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EthereumTxRequest.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EthereumTxRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EthereumTxRequest.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EthereumTxRequest) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EthereumTxRequest.Merge(m, src)
|
||||
}
|
||||
func (m *EthereumTxRequest) XXX_Size() int {
|
||||
return xxx_messageInfo_EthereumTxRequest.Size(m)
|
||||
}
|
||||
func (m *EthereumTxRequest) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EthereumTxRequest.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EthereumTxRequest proto.InternalMessageInfo
|
||||
|
||||
func (m *EthereumTxRequest) GetDataLength() uint32 {
|
||||
if m != nil && m.DataLength != nil {
|
||||
return *m.DataLength
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *EthereumTxRequest) GetSignatureV() uint32 {
|
||||
if m != nil && m.SignatureV != nil {
|
||||
return *m.SignatureV
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *EthereumTxRequest) GetSignatureR() []byte {
|
||||
if m != nil {
|
||||
return m.SignatureR
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumTxRequest) GetSignatureS() []byte {
|
||||
if m != nil {
|
||||
return m.SignatureS
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//*
|
||||
// Request: Transaction payload data.
|
||||
// @next EthereumTxRequest
|
||||
type EthereumTxAck struct {
|
||||
DataChunk []byte `protobuf:"bytes,1,opt,name=data_chunk,json=dataChunk" json:"data_chunk,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EthereumTxAck) Reset() { *m = EthereumTxAck{} }
|
||||
func (m *EthereumTxAck) String() string { return proto.CompactTextString(m) }
|
||||
func (*EthereumTxAck) ProtoMessage() {}
|
||||
func (*EthereumTxAck) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_cb33f46ba915f15c, []int{6}
|
||||
}
|
||||
|
||||
func (m *EthereumTxAck) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EthereumTxAck.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EthereumTxAck) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EthereumTxAck.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EthereumTxAck) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EthereumTxAck.Merge(m, src)
|
||||
}
|
||||
func (m *EthereumTxAck) XXX_Size() int {
|
||||
return xxx_messageInfo_EthereumTxAck.Size(m)
|
||||
}
|
||||
func (m *EthereumTxAck) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EthereumTxAck.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EthereumTxAck proto.InternalMessageInfo
|
||||
|
||||
func (m *EthereumTxAck) GetDataChunk() []byte {
|
||||
if m != nil {
|
||||
return m.DataChunk
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//*
|
||||
// Request: Ask device to sign message
|
||||
// @start
|
||||
// @next EthereumMessageSignature
|
||||
// @next Failure
|
||||
type EthereumSignMessage struct {
|
||||
AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"`
|
||||
Message []byte `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EthereumSignMessage) Reset() { *m = EthereumSignMessage{} }
|
||||
func (m *EthereumSignMessage) String() string { return proto.CompactTextString(m) }
|
||||
func (*EthereumSignMessage) ProtoMessage() {}
|
||||
func (*EthereumSignMessage) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_cb33f46ba915f15c, []int{7}
|
||||
}
|
||||
|
||||
func (m *EthereumSignMessage) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EthereumSignMessage.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EthereumSignMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EthereumSignMessage.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EthereumSignMessage) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EthereumSignMessage.Merge(m, src)
|
||||
}
|
||||
func (m *EthereumSignMessage) XXX_Size() int {
|
||||
return xxx_messageInfo_EthereumSignMessage.Size(m)
|
||||
}
|
||||
func (m *EthereumSignMessage) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EthereumSignMessage.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EthereumSignMessage proto.InternalMessageInfo
|
||||
|
||||
func (m *EthereumSignMessage) GetAddressN() []uint32 {
|
||||
if m != nil {
|
||||
return m.AddressN
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumSignMessage) GetMessage() []byte {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//*
|
||||
// Response: Signed message
|
||||
// @end
|
||||
type EthereumMessageSignature struct {
|
||||
AddressBin []byte `protobuf:"bytes,1,opt,name=addressBin" json:"addressBin,omitempty"`
|
||||
Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,omitempty"`
|
||||
AddressHex *string `protobuf:"bytes,3,opt,name=addressHex" json:"addressHex,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EthereumMessageSignature) Reset() { *m = EthereumMessageSignature{} }
|
||||
func (m *EthereumMessageSignature) String() string { return proto.CompactTextString(m) }
|
||||
func (*EthereumMessageSignature) ProtoMessage() {}
|
||||
func (*EthereumMessageSignature) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_cb33f46ba915f15c, []int{8}
|
||||
}
|
||||
|
||||
func (m *EthereumMessageSignature) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EthereumMessageSignature.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EthereumMessageSignature) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EthereumMessageSignature.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EthereumMessageSignature) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EthereumMessageSignature.Merge(m, src)
|
||||
}
|
||||
func (m *EthereumMessageSignature) XXX_Size() int {
|
||||
return xxx_messageInfo_EthereumMessageSignature.Size(m)
|
||||
}
|
||||
func (m *EthereumMessageSignature) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EthereumMessageSignature.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EthereumMessageSignature proto.InternalMessageInfo
|
||||
|
||||
func (m *EthereumMessageSignature) GetAddressBin() []byte {
|
||||
if m != nil {
|
||||
return m.AddressBin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumMessageSignature) GetSignature() []byte {
|
||||
if m != nil {
|
||||
return m.Signature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumMessageSignature) GetAddressHex() string {
|
||||
if m != nil && m.AddressHex != nil {
|
||||
return *m.AddressHex
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
//*
|
||||
// Request: Ask device to verify message
|
||||
// @start
|
||||
// @next Success
|
||||
// @next Failure
|
||||
type EthereumVerifyMessage struct {
|
||||
AddressBin []byte `protobuf:"bytes,1,opt,name=addressBin" json:"addressBin,omitempty"`
|
||||
Signature []byte `protobuf:"bytes,2,opt,name=signature" json:"signature,omitempty"`
|
||||
Message []byte `protobuf:"bytes,3,opt,name=message" json:"message,omitempty"`
|
||||
AddressHex *string `protobuf:"bytes,4,opt,name=addressHex" json:"addressHex,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *EthereumVerifyMessage) Reset() { *m = EthereumVerifyMessage{} }
|
||||
func (m *EthereumVerifyMessage) String() string { return proto.CompactTextString(m) }
|
||||
func (*EthereumVerifyMessage) ProtoMessage() {}
|
||||
func (*EthereumVerifyMessage) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_cb33f46ba915f15c, []int{9}
|
||||
}
|
||||
|
||||
func (m *EthereumVerifyMessage) XXX_Unmarshal(b []byte) error {
|
||||
return xxx_messageInfo_EthereumVerifyMessage.Unmarshal(m, b)
|
||||
}
|
||||
func (m *EthereumVerifyMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
return xxx_messageInfo_EthereumVerifyMessage.Marshal(b, m, deterministic)
|
||||
}
|
||||
func (m *EthereumVerifyMessage) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_EthereumVerifyMessage.Merge(m, src)
|
||||
}
|
||||
func (m *EthereumVerifyMessage) XXX_Size() int {
|
||||
return xxx_messageInfo_EthereumVerifyMessage.Size(m)
|
||||
}
|
||||
func (m *EthereumVerifyMessage) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_EthereumVerifyMessage.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_EthereumVerifyMessage proto.InternalMessageInfo
|
||||
|
||||
func (m *EthereumVerifyMessage) GetAddressBin() []byte {
|
||||
if m != nil {
|
||||
return m.AddressBin
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumVerifyMessage) GetSignature() []byte {
|
||||
if m != nil {
|
||||
return m.Signature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumVerifyMessage) GetMessage() []byte {
|
||||
if m != nil {
|
||||
return m.Message
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *EthereumVerifyMessage) GetAddressHex() string {
|
||||
if m != nil && m.AddressHex != nil {
|
||||
return *m.AddressHex
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*EthereumGetPublicKey)(nil), "hw.trezor.messages.ethereum.EthereumGetPublicKey")
|
||||
proto.RegisterType((*EthereumPublicKey)(nil), "hw.trezor.messages.ethereum.EthereumPublicKey")
|
||||
proto.RegisterType((*EthereumGetAddress)(nil), "hw.trezor.messages.ethereum.EthereumGetAddress")
|
||||
proto.RegisterType((*EthereumAddress)(nil), "hw.trezor.messages.ethereum.EthereumAddress")
|
||||
proto.RegisterType((*EthereumSignTx)(nil), "hw.trezor.messages.ethereum.EthereumSignTx")
|
||||
proto.RegisterType((*EthereumTxRequest)(nil), "hw.trezor.messages.ethereum.EthereumTxRequest")
|
||||
proto.RegisterType((*EthereumTxAck)(nil), "hw.trezor.messages.ethereum.EthereumTxAck")
|
||||
proto.RegisterType((*EthereumSignMessage)(nil), "hw.trezor.messages.ethereum.EthereumSignMessage")
|
||||
proto.RegisterType((*EthereumMessageSignature)(nil), "hw.trezor.messages.ethereum.EthereumMessageSignature")
|
||||
proto.RegisterType((*EthereumVerifyMessage)(nil), "hw.trezor.messages.ethereum.EthereumVerifyMessage")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("messages-ethereum.proto", fileDescriptor_cb33f46ba915f15c) }
|
||||
|
||||
var fileDescriptor_cb33f46ba915f15c = []byte{
|
||||
// 593 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0x4d, 0x6f, 0xd3, 0x40,
|
||||
0x10, 0x95, 0x9b, 0xb4, 0x49, 0x26, 0x0d, 0x1f, 0xa6, 0x55, 0x17, 0x0a, 0x34, 0x18, 0x21, 0xe5,
|
||||
0x00, 0x3e, 0x70, 0x43, 0xe2, 0xd2, 0x52, 0x44, 0x2b, 0x4a, 0x55, 0xdc, 0xa8, 0x57, 0x6b, 0x63,
|
||||
0x6f, 0xe3, 0x55, 0x9d, 0xdd, 0xe0, 0x5d, 0xb7, 0x0e, 0x7f, 0x82, 0x23, 0xff, 0x87, 0x5f, 0x86,
|
||||
0xf6, 0x2b, 0x71, 0x52, 0x54, 0x0e, 0xbd, 0x65, 0xde, 0xbc, 0x7d, 0xf3, 0x66, 0xf4, 0x62, 0xd8,
|
||||
0x99, 0x10, 0x21, 0xf0, 0x98, 0x88, 0x77, 0x44, 0x66, 0xa4, 0x20, 0xe5, 0x24, 0x9c, 0x16, 0x5c,
|
||||
0x72, 0x7f, 0x37, 0xbb, 0x09, 0x65, 0x41, 0x7e, 0xf2, 0x22, 0x74, 0x94, 0xd0, 0x51, 0x9e, 0x6d,
|
||||
0xcf, 0x5f, 0x25, 0x7c, 0x32, 0xe1, 0xcc, 0xbc, 0x09, 0x2e, 0x60, 0xeb, 0xb3, 0xa5, 0x7c, 0x21,
|
||||
0xf2, 0xac, 0x1c, 0xe5, 0x34, 0xf9, 0x4a, 0x66, 0xfe, 0x2e, 0x74, 0x70, 0x9a, 0x16, 0x44, 0x88,
|
||||
0x98, 0x21, 0xaf, 0xdf, 0x18, 0xf4, 0xa2, 0xb6, 0x05, 0x4e, 0xfd, 0x57, 0xb0, 0x29, 0x32, 0x7e,
|
||||
0x13, 0xa7, 0x54, 0x4c, 0x73, 0x3c, 0x43, 0x6b, 0x7d, 0x6f, 0xd0, 0x8e, 0xba, 0x0a, 0x3b, 0x34,
|
||||
0x50, 0x30, 0x82, 0xc7, 0x4e, 0x77, 0x21, 0xfa, 0x01, 0x9a, 0x8c, 0xa7, 0x04, 0x79, 0x7d, 0x6f,
|
||||
0xd0, 0x7d, 0xff, 0x26, 0xfc, 0x87, 0x5f, 0x6b, 0xee, 0xe8, 0xf0, 0x94, 0xa7, 0x64, 0x38, 0x9b,
|
||||
0x92, 0x48, 0x3f, 0xf1, 0x7d, 0x68, 0x56, 0xd3, 0x72, 0xa4, 0x47, 0x75, 0x22, 0xfd, 0x3b, 0x18,
|
||||
0x82, 0x5f, 0xf3, 0xbe, 0x6f, 0xdc, 0xdd, 0xdb, 0xf9, 0x77, 0x78, 0xe8, 0x54, 0x9d, 0xe4, 0x4b,
|
||||
0x00, 0xab, 0x70, 0x40, 0x99, 0x76, 0xbf, 0x19, 0xd5, 0x90, 0x5a, 0xff, 0x88, 0x54, 0xd6, 0x62,
|
||||
0x0d, 0x09, 0xfe, 0xac, 0xc1, 0x03, 0xa7, 0x79, 0x4e, 0xc7, 0x6c, 0x58, 0xdd, 0xed, 0x72, 0x0b,
|
||||
0xd6, 0x19, 0x67, 0x09, 0xd1, 0x52, 0x9b, 0x91, 0x29, 0xd4, 0x93, 0x31, 0x16, 0xf1, 0xb4, 0xa0,
|
||||
0x09, 0x41, 0x0d, 0xdd, 0x69, 0x8f, 0xb1, 0x38, 0x53, 0xb5, 0x6b, 0xe6, 0x74, 0x42, 0x25, 0x6a,
|
||||
0xce, 0x9b, 0x27, 0xaa, 0x56, 0x7a, 0x92, 0x2b, 0xeb, 0xeb, 0x46, 0x4f, 0x17, 0x06, 0x55, 0x86,
|
||||
0xbb, 0xda, 0xb0, 0x29, 0x14, 0x7a, 0x8d, 0xf3, 0x92, 0xa0, 0x0d, 0xc3, 0xd5, 0x85, 0xff, 0x16,
|
||||
0xfc, 0x14, 0x4b, 0x1c, 0x53, 0x46, 0x25, 0xc5, 0x79, 0x9c, 0x64, 0x25, 0xbb, 0x42, 0x2d, 0x4d,
|
||||
0x79, 0xa4, 0x3a, 0xc7, 0xa6, 0xf1, 0x49, 0xe1, 0xfe, 0x1e, 0x74, 0x35, 0x3b, 0x27, 0x6c, 0x2c,
|
||||
0x33, 0xd4, 0xee, 0x7b, 0x83, 0x5e, 0x04, 0x0a, 0x3a, 0xd1, 0x88, 0xff, 0x14, 0xda, 0x49, 0x86,
|
||||
0x29, 0x8b, 0x69, 0x8a, 0x3a, 0xba, 0xdb, 0xd2, 0xf5, 0x71, 0xea, 0xef, 0x40, 0x4b, 0x56, 0xb1,
|
||||
0x9c, 0x4d, 0x09, 0x02, 0xdd, 0xd9, 0x90, 0x95, 0xca, 0x41, 0xf0, 0xdb, 0x5b, 0x44, 0x6a, 0x58,
|
||||
0x45, 0xe4, 0x47, 0x49, 0x84, 0x5c, 0x1d, 0xe5, 0xdd, 0x1a, 0xb5, 0x07, 0x5d, 0x41, 0xc7, 0x0c,
|
||||
0xcb, 0xb2, 0x20, 0xf1, 0xb5, 0xbe, 0x68, 0x2f, 0x82, 0x39, 0x74, 0xb1, 0x4c, 0x28, 0xec, 0x61,
|
||||
0x17, 0x84, 0x68, 0x99, 0x20, 0xec, 0x71, 0x17, 0x84, 0xf3, 0x20, 0x84, 0xde, 0xc2, 0xd8, 0x7e,
|
||||
0x72, 0xe5, 0xbf, 0x00, 0xed, 0xc0, 0x5e, 0xc9, 0xe4, 0xa5, 0xa3, 0x10, 0x7d, 0x9e, 0xe0, 0x04,
|
||||
0x9e, 0xd4, 0xd3, 0xf0, 0xcd, 0x64, 0xff, 0xee, 0x48, 0x20, 0x68, 0xd9, 0xff, 0x88, 0x0d, 0x85,
|
||||
0x2b, 0x83, 0x0a, 0x90, 0x53, 0xb3, 0x4a, 0xe7, 0xce, 0xda, 0x7f, 0x83, 0xfb, 0x1c, 0x3a, 0xf3,
|
||||
0x3d, 0xac, 0xee, 0x02, 0x58, 0x89, 0x75, 0xe3, 0x56, 0xac, 0x7f, 0x79, 0xb0, 0xed, 0x46, 0x5f,
|
||||
0x90, 0x82, 0x5e, 0xce, 0xdc, 0x2a, 0xf7, 0x9b, 0x5b, 0xdb, 0xb5, 0xb1, 0xb4, 0xeb, 0x8a, 0xa3,
|
||||
0xe6, 0xaa, 0xa3, 0x83, 0x8f, 0xf0, 0x3a, 0xe1, 0x93, 0x50, 0x60, 0xc9, 0x45, 0x46, 0x73, 0x3c,
|
||||
0x12, 0xee, 0x03, 0x93, 0xd3, 0x91, 0xf9, 0xe2, 0x8d, 0xca, 0xcb, 0x83, 0xed, 0xa1, 0x06, 0xad,
|
||||
0x5b, 0xb7, 0xc2, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8a, 0xce, 0x81, 0xc8, 0x59, 0x05, 0x00,
|
||||
0x00,
|
||||
}
|
||||
131
accounts/usbwallet/trezor/messages-ethereum.proto
Normal file
131
accounts/usbwallet/trezor/messages-ethereum.proto
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||
// https://github.com/trezor/trezor-common/blob/master/protob/messages-ethereum.proto
|
||||
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9.
|
||||
|
||||
syntax = "proto2";
|
||||
package hw.trezor.messages.ethereum;
|
||||
|
||||
// Sugar for easier handling in Java
|
||||
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||
option java_outer_classname = "TrezorMessageEthereum";
|
||||
|
||||
import "messages-common.proto";
|
||||
|
||||
|
||||
/**
|
||||
* Request: Ask device for public key corresponding to address_n path
|
||||
* @start
|
||||
* @next EthereumPublicKey
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumGetPublicKey {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bool show_display = 2; // optionally show on display before sending the result
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Contains public key derived from device private seed
|
||||
* @end
|
||||
*/
|
||||
message EthereumPublicKey {
|
||||
optional hw.trezor.messages.common.HDNodeType node = 1; // BIP32 public node
|
||||
optional string xpub = 2; // serialized form of public node
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device for Ethereum address corresponding to address_n path
|
||||
* @start
|
||||
* @next EthereumAddress
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumGetAddress {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bool show_display = 2; // optionally show on display before sending the result
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Contains an Ethereum address derived from device private seed
|
||||
* @end
|
||||
*/
|
||||
message EthereumAddress {
|
||||
optional bytes addressBin = 1; // Ethereum address as 20 bytes (legacy firmwares)
|
||||
optional string addressHex = 2; // Ethereum address as hex string (newer firmwares)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to sign transaction
|
||||
* All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing.
|
||||
* Note: the first at most 1024 bytes of data MUST be transmitted as part of this message.
|
||||
* @start
|
||||
* @next EthereumTxRequest
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumSignTx {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bytes nonce = 2; // <=256 bit unsigned big endian
|
||||
optional bytes gas_price = 3; // <=256 bit unsigned big endian (in wei)
|
||||
optional bytes gas_limit = 4; // <=256 bit unsigned big endian
|
||||
optional bytes toBin = 5; // recipient address (20 bytes, legacy firmware)
|
||||
optional string toHex = 11; // recipient address (hex string, newer firmware)
|
||||
optional bytes value = 6; // <=256 bit unsigned big endian (in wei)
|
||||
optional bytes data_initial_chunk = 7; // The initial data chunk (<= 1024 bytes)
|
||||
optional uint32 data_length = 8; // Length of transaction payload
|
||||
optional uint32 chain_id = 9; // Chain Id for EIP 155
|
||||
optional uint32 tx_type = 10; // (only for Wanchain)
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device asks for more data from transaction payload, or returns the signature.
|
||||
* If data_length is set, device awaits that many more bytes of payload.
|
||||
* Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present.
|
||||
* @end
|
||||
* @next EthereumTxAck
|
||||
*/
|
||||
message EthereumTxRequest {
|
||||
optional uint32 data_length = 1; // Number of bytes being requested (<= 1024)
|
||||
optional uint32 signature_v = 2; // Computed signature (recovery parameter, limited to 27 or 28)
|
||||
optional bytes signature_r = 3; // Computed signature R component (256 bit)
|
||||
optional bytes signature_s = 4; // Computed signature S component (256 bit)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Transaction payload data.
|
||||
* @next EthereumTxRequest
|
||||
*/
|
||||
message EthereumTxAck {
|
||||
optional bytes data_chunk = 1; // Bytes from transaction payload (<= 1024 bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to sign message
|
||||
* @start
|
||||
* @next EthereumMessageSignature
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumSignMessage {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bytes message = 2; // message to be signed
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Signed message
|
||||
* @end
|
||||
*/
|
||||
message EthereumMessageSignature {
|
||||
optional bytes addressBin = 1; // address used to sign the message (20 bytes, legacy firmware)
|
||||
optional bytes signature = 2; // signature of the message
|
||||
optional string addressHex = 3; // address used to sign the message (hex string, newer firmware)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to verify message
|
||||
* @start
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumVerifyMessage {
|
||||
optional bytes addressBin = 1; // address to verify (20 bytes, legacy firmware)
|
||||
optional bytes signature = 2; // signature to verify
|
||||
optional bytes message = 3; // message to verify
|
||||
optional string addressHex = 4; // address to verify (hex string, newer firmware)
|
||||
}
|
||||
1621
accounts/usbwallet/trezor/messages-management.pb.go
Normal file
1621
accounts/usbwallet/trezor/messages-management.pb.go
Normal file
File diff suppressed because it is too large
Load diff
289
accounts/usbwallet/trezor/messages-management.proto
Normal file
289
accounts/usbwallet/trezor/messages-management.proto
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||
// https://github.com/trezor/trezor-common/blob/master/protob/messages-management.proto
|
||||
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9.
|
||||
|
||||
syntax = "proto2";
|
||||
package hw.trezor.messages.management;
|
||||
|
||||
// Sugar for easier handling in Java
|
||||
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||
option java_outer_classname = "TrezorMessageManagement";
|
||||
|
||||
import "messages-common.proto";
|
||||
|
||||
/**
|
||||
* Request: Reset device to default state and ask for device details
|
||||
* @start
|
||||
* @next Features
|
||||
*/
|
||||
message Initialize {
|
||||
optional bytes state = 1; // assumed device state, clear session if set and different
|
||||
optional bool skip_passphrase = 2; // this session should always assume empty passphrase
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask for device details (no device reset)
|
||||
* @start
|
||||
* @next Features
|
||||
*/
|
||||
message GetFeatures {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Reports various information about the device
|
||||
* @end
|
||||
*/
|
||||
message Features {
|
||||
optional string vendor = 1; // name of the manufacturer, e.g. "trezor.io"
|
||||
optional uint32 major_version = 2; // major version of the firmware/bootloader, e.g. 1
|
||||
optional uint32 minor_version = 3; // minor version of the firmware/bootloader, e.g. 0
|
||||
optional uint32 patch_version = 4; // patch version of the firmware/bootloader, e.g. 0
|
||||
optional bool bootloader_mode = 5; // is device in bootloader mode?
|
||||
optional string device_id = 6; // device's unique identifier
|
||||
optional bool pin_protection = 7; // is device protected by PIN?
|
||||
optional bool passphrase_protection = 8; // is node/mnemonic encrypted using passphrase?
|
||||
optional string language = 9; // device language
|
||||
optional string label = 10; // device description label
|
||||
optional bool initialized = 12; // does device contain seed?
|
||||
optional bytes revision = 13; // SCM revision of firmware
|
||||
optional bytes bootloader_hash = 14; // hash of the bootloader
|
||||
optional bool imported = 15; // was storage imported from an external source?
|
||||
optional bool pin_cached = 16; // is PIN already cached in session?
|
||||
optional bool passphrase_cached = 17; // is passphrase already cached in session?
|
||||
optional bool firmware_present = 18; // is valid firmware loaded?
|
||||
optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup)
|
||||
optional uint32 flags = 20; // device flags (equals to Storage.flags)
|
||||
optional string model = 21; // device hardware model
|
||||
optional uint32 fw_major = 22; // reported firmware version if in bootloader mode
|
||||
optional uint32 fw_minor = 23; // reported firmware version if in bootloader mode
|
||||
optional uint32 fw_patch = 24; // reported firmware version if in bootloader mode
|
||||
optional string fw_vendor = 25; // reported firmware vendor if in bootloader mode
|
||||
optional bytes fw_vendor_keys = 26; // reported firmware vendor keys (their hash)
|
||||
optional bool unfinished_backup = 27; // report unfinished backup (equals to Storage.unfinished_backup)
|
||||
optional bool no_backup = 28; // report no backup (equals to Storage.no_backup)
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: clear session (removes cached PIN, passphrase, etc).
|
||||
* @start
|
||||
* @next Success
|
||||
*/
|
||||
message ClearSession {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: change language and/or label of the device
|
||||
* @start
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message ApplySettings {
|
||||
optional string language = 1;
|
||||
optional string label = 2;
|
||||
optional bool use_passphrase = 3;
|
||||
optional bytes homescreen = 4;
|
||||
optional PassphraseSourceType passphrase_source = 5;
|
||||
optional uint32 auto_lock_delay_ms = 6;
|
||||
optional uint32 display_rotation = 7; // in degrees from North
|
||||
/**
|
||||
* Structure representing passphrase source
|
||||
*/
|
||||
enum PassphraseSourceType {
|
||||
ASK = 0;
|
||||
DEVICE = 1;
|
||||
HOST = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: set flags of the device
|
||||
* @start
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message ApplyFlags {
|
||||
optional uint32 flags = 1; // bitmask, can only set bits, not unset
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Starts workflow for setting/changing/removing the PIN
|
||||
* @start
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message ChangePin {
|
||||
optional bool remove = 1; // is PIN removal requested?
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Test if the device is alive, device sends back the message in Success response
|
||||
* @start
|
||||
* @next Success
|
||||
*/
|
||||
message Ping {
|
||||
optional string message = 1; // message to send back in Success message
|
||||
optional bool button_protection = 2; // ask for button press
|
||||
optional bool pin_protection = 3; // ask for PIN if set in device
|
||||
optional bool passphrase_protection = 4; // ask for passphrase if set in device
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Abort last operation that required user interaction
|
||||
* @start
|
||||
* @next Failure
|
||||
*/
|
||||
message Cancel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Request a sample of random data generated by hardware RNG. May be used for testing.
|
||||
* @start
|
||||
* @next Entropy
|
||||
* @next Failure
|
||||
*/
|
||||
message GetEntropy {
|
||||
required uint32 size = 1; // size of requested entropy
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Reply with random data generated by internal RNG
|
||||
* @end
|
||||
*/
|
||||
message Entropy {
|
||||
required bytes entropy = 1; // chunk of random generated bytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Request device to wipe all sensitive data and settings
|
||||
* @start
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message WipeDevice {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Load seed and related internal settings from the computer
|
||||
* @start
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message LoadDevice {
|
||||
optional string mnemonic = 1; // seed encoded as BIP-39 mnemonic (12, 18 or 24 words)
|
||||
optional hw.trezor.messages.common.HDNodeType node = 2; // BIP-32 node
|
||||
optional string pin = 3; // set PIN protection
|
||||
optional bool passphrase_protection = 4; // enable master node encryption using passphrase
|
||||
optional string language = 5 [default='english']; // device language
|
||||
optional string label = 6; // device label
|
||||
optional bool skip_checksum = 7; // do not test mnemonic for valid BIP-39 checksum
|
||||
optional uint32 u2f_counter = 8; // U2F counter
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to do initialization involving user interaction
|
||||
* @start
|
||||
* @next EntropyRequest
|
||||
* @next Failure
|
||||
*/
|
||||
message ResetDevice {
|
||||
optional bool display_random = 1; // display entropy generated by the device before asking for additional entropy
|
||||
optional uint32 strength = 2 [default=256]; // strength of seed in bits
|
||||
optional bool passphrase_protection = 3; // enable master node encryption using passphrase
|
||||
optional bool pin_protection = 4; // enable PIN protection
|
||||
optional string language = 5 [default='english']; // device language
|
||||
optional string label = 6; // device label
|
||||
optional uint32 u2f_counter = 7; // U2F counter
|
||||
optional bool skip_backup = 8; // postpone seed backup to BackupDevice workflow
|
||||
optional bool no_backup = 9; // indicate that no backup is going to be made
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Perform backup of the device seed if not backed up using ResetDevice
|
||||
* @start
|
||||
* @next Success
|
||||
*/
|
||||
message BackupDevice {
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Ask for additional entropy from host computer
|
||||
* @next EntropyAck
|
||||
*/
|
||||
message EntropyRequest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Provide additional entropy for seed generation function
|
||||
* @next Success
|
||||
*/
|
||||
message EntropyAck {
|
||||
optional bytes entropy = 1; // 256 bits (32 bytes) of random data
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Start recovery workflow asking user for specific words of mnemonic
|
||||
* Used to recovery device safely even on untrusted computer.
|
||||
* @start
|
||||
* @next WordRequest
|
||||
*/
|
||||
message RecoveryDevice {
|
||||
optional uint32 word_count = 1; // number of words in BIP-39 mnemonic
|
||||
optional bool passphrase_protection = 2; // enable master node encryption using passphrase
|
||||
optional bool pin_protection = 3; // enable PIN protection
|
||||
optional string language = 4 [default='english']; // device language
|
||||
optional string label = 5; // device label
|
||||
optional bool enforce_wordlist = 6; // enforce BIP-39 wordlist during the process
|
||||
// 7 reserved for unused recovery method
|
||||
optional RecoveryDeviceType type = 8; // supported recovery type
|
||||
optional uint32 u2f_counter = 9; // U2F counter
|
||||
optional bool dry_run = 10; // perform dry-run recovery workflow (for safe mnemonic validation)
|
||||
/**
|
||||
* Type of recovery procedure. These should be used as bitmask, e.g.,
|
||||
* `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix`
|
||||
* listing every method supported by the host computer.
|
||||
*
|
||||
* Note that ScrambledWords must be supported by every implementation
|
||||
* for backward compatibility; there is no way to not support it.
|
||||
*/
|
||||
enum RecoveryDeviceType {
|
||||
// use powers of two when extending this field
|
||||
RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order
|
||||
RecoveryDeviceType_Matrix = 1; // matrix recovery type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Device is waiting for user to enter word of the mnemonic
|
||||
* Its position is shown only on device's internal display.
|
||||
* @next WordAck
|
||||
*/
|
||||
message WordRequest {
|
||||
optional WordRequestType type = 1;
|
||||
/**
|
||||
* Type of Recovery Word request
|
||||
*/
|
||||
enum WordRequestType {
|
||||
WordRequestType_Plain = 0;
|
||||
WordRequestType_Matrix9 = 1;
|
||||
WordRequestType_Matrix6 = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Computer replies with word from the mnemonic
|
||||
* @next WordRequest
|
||||
* @next Success
|
||||
* @next Failure
|
||||
*/
|
||||
message WordAck {
|
||||
required string word = 1; // one word of mnemonic on asked position
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Set U2F counter
|
||||
* @start
|
||||
* @next Success
|
||||
*/
|
||||
message SetU2FCounter {
|
||||
optional uint32 u2f_counter = 1; // counter
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
|
|
@ -16,11 +16,35 @@
|
|||
|
||||
// This file contains the implementation for interacting with the Trezor hardware
|
||||
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
||||
// https://doc.satoshilabs.com/trezor-tech/api-protobuf.html
|
||||
// https://wiki.trezor.io/Developers_guide-Message_Workflows
|
||||
|
||||
//go:generate protoc --go_out=import_path=trezor:. types.proto messages.proto
|
||||
// !!! STAHP !!!
|
||||
//
|
||||
// Before you touch the protocol files, you need to be aware of a breaking change
|
||||
// that occurred between firmware versions 1.7.3->1.8.0 (Model One) and 2.0.10->
|
||||
// 2.1.0 (Model T). The Ethereum address representation was changed from the 20
|
||||
// byte binary blob to a 42 byte hex string. The upstream protocol buffer files
|
||||
// only support the new format, so blindly pulling in a new spec will break old
|
||||
// devices!
|
||||
//
|
||||
// The Trezor devs had the foresight to add the string version as a new message
|
||||
// code instead of replacing the binary one. This means that the proto file can
|
||||
// actually define both the old and the new versions as optional. Please ensure
|
||||
// that you add back the old addresses everywhere (to avoid name clash. use the
|
||||
// addressBin and addressHex names).
|
||||
//
|
||||
// If in doubt, reach out to @karalabe.
|
||||
|
||||
// Package trezor contains the wire protocol wrapper in Go.
|
||||
// To regenerate the protocol files in this package:
|
||||
// - Download the latest protoc https://github.com/protocolbuffers/protobuf/releases
|
||||
// - Build with the usual `./configure && make` and ensure it's on your $PATH
|
||||
// - Delete all the .proto and .pb.go files, pull in fresh ones from Trezor
|
||||
// - Grab the latest Go plugin `go get -u github.com/golang/protobuf/protoc-gen-go`
|
||||
// - Vendor in the latest Go plugin `govendor fetch github.com/golang/protobuf/...`
|
||||
|
||||
//go:generate protoc -I/usr/local/include:. --go_out=import_path=trezor:. messages.proto messages-common.proto messages-management.proto messages-ethereum.proto
|
||||
|
||||
// Package trezor contains the wire protocol.
|
||||
package trezor
|
||||
|
||||
import (
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,278 +0,0 @@
|
|||
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||
// https://github.com/trezor/trezor-common/blob/master/protob/types.proto
|
||||
// dated 28.07.2017, commit dd8ec3231fb5f7992360aff9bdfe30bb58130f4b.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
/**
|
||||
* Types for TREZOR communication
|
||||
*
|
||||
* @author Marek Palatinus <slush@satoshilabs.com>
|
||||
* @version 1.2
|
||||
*/
|
||||
|
||||
// Sugar for easier handling in Java
|
||||
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||
option java_outer_classname = "TrezorType";
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
/**
|
||||
* Options for specifying message direction and type of wire (normal/debug)
|
||||
*/
|
||||
extend google.protobuf.EnumValueOptions {
|
||||
optional bool wire_in = 50002; // message can be transmitted via wire from PC to TREZOR
|
||||
optional bool wire_out = 50003; // message can be transmitted via wire from TREZOR to PC
|
||||
optional bool wire_debug_in = 50004; // message can be transmitted via debug wire from PC to TREZOR
|
||||
optional bool wire_debug_out = 50005; // message can be transmitted via debug wire from TREZOR to PC
|
||||
optional bool wire_tiny = 50006; // message is handled by TREZOR when the USB stack is in tiny mode
|
||||
optional bool wire_bootloader = 50007; // message is only handled by TREZOR Bootloader
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of failures returned by Failure message
|
||||
* @used_in Failure
|
||||
*/
|
||||
enum FailureType {
|
||||
Failure_UnexpectedMessage = 1;
|
||||
Failure_ButtonExpected = 2;
|
||||
Failure_DataError = 3;
|
||||
Failure_ActionCancelled = 4;
|
||||
Failure_PinExpected = 5;
|
||||
Failure_PinCancelled = 6;
|
||||
Failure_PinInvalid = 7;
|
||||
Failure_InvalidSignature = 8;
|
||||
Failure_ProcessError = 9;
|
||||
Failure_NotEnoughFunds = 10;
|
||||
Failure_NotInitialized = 11;
|
||||
Failure_FirmwareError = 99;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of script which will be used for transaction output
|
||||
* @used_in TxOutputType
|
||||
*/
|
||||
enum OutputScriptType {
|
||||
PAYTOADDRESS = 0; // used for all addresses (bitcoin, p2sh, witness)
|
||||
PAYTOSCRIPTHASH = 1; // p2sh address (deprecated; use PAYTOADDRESS)
|
||||
PAYTOMULTISIG = 2; // only for change output
|
||||
PAYTOOPRETURN = 3; // op_return
|
||||
PAYTOWITNESS = 4; // only for change output
|
||||
PAYTOP2SHWITNESS = 5; // only for change output
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of script which will be used for transaction output
|
||||
* @used_in TxInputType
|
||||
*/
|
||||
enum InputScriptType {
|
||||
SPENDADDRESS = 0; // standard p2pkh address
|
||||
SPENDMULTISIG = 1; // p2sh multisig address
|
||||
EXTERNAL = 2; // reserved for external inputs (coinjoin)
|
||||
SPENDWITNESS = 3; // native segwit
|
||||
SPENDP2SHWITNESS = 4; // segwit over p2sh (backward compatible)
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of information required by transaction signing process
|
||||
* @used_in TxRequest
|
||||
*/
|
||||
enum RequestType {
|
||||
TXINPUT = 0;
|
||||
TXOUTPUT = 1;
|
||||
TXMETA = 2;
|
||||
TXFINISHED = 3;
|
||||
TXEXTRADATA = 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of button request
|
||||
* @used_in ButtonRequest
|
||||
*/
|
||||
enum ButtonRequestType {
|
||||
ButtonRequest_Other = 1;
|
||||
ButtonRequest_FeeOverThreshold = 2;
|
||||
ButtonRequest_ConfirmOutput = 3;
|
||||
ButtonRequest_ResetDevice = 4;
|
||||
ButtonRequest_ConfirmWord = 5;
|
||||
ButtonRequest_WipeDevice = 6;
|
||||
ButtonRequest_ProtectCall = 7;
|
||||
ButtonRequest_SignTx = 8;
|
||||
ButtonRequest_FirmwareCheck = 9;
|
||||
ButtonRequest_Address = 10;
|
||||
ButtonRequest_PublicKey = 11;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of PIN request
|
||||
* @used_in PinMatrixRequest
|
||||
*/
|
||||
enum PinMatrixRequestType {
|
||||
PinMatrixRequestType_Current = 1;
|
||||
PinMatrixRequestType_NewFirst = 2;
|
||||
PinMatrixRequestType_NewSecond = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of recovery procedure. These should be used as bitmask, e.g.,
|
||||
* `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix`
|
||||
* listing every method supported by the host computer.
|
||||
*
|
||||
* Note that ScrambledWords must be supported by every implementation
|
||||
* for backward compatibility; there is no way to not support it.
|
||||
*
|
||||
* @used_in RecoveryDevice
|
||||
*/
|
||||
enum RecoveryDeviceType {
|
||||
// use powers of two when extending this field
|
||||
RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order
|
||||
RecoveryDeviceType_Matrix = 1; // matrix recovery type
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of Recovery Word request
|
||||
* @used_in WordRequest
|
||||
*/
|
||||
enum WordRequestType {
|
||||
WordRequestType_Plain = 0;
|
||||
WordRequestType_Matrix9 = 1;
|
||||
WordRequestType_Matrix6 = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing BIP32 (hierarchical deterministic) node
|
||||
* Used for imports of private key into the device and exporting public key out of device
|
||||
* @used_in PublicKey
|
||||
* @used_in LoadDevice
|
||||
* @used_in DebugLinkState
|
||||
* @used_in Storage
|
||||
*/
|
||||
message HDNodeType {
|
||||
required uint32 depth = 1;
|
||||
required uint32 fingerprint = 2;
|
||||
required uint32 child_num = 3;
|
||||
required bytes chain_code = 4;
|
||||
optional bytes private_key = 5;
|
||||
optional bytes public_key = 6;
|
||||
}
|
||||
|
||||
message HDNodePathType {
|
||||
required HDNodeType node = 1; // BIP-32 node in deserialized form
|
||||
repeated uint32 address_n = 2; // BIP-32 path to derive the key from node
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing Coin
|
||||
* @used_in Features
|
||||
*/
|
||||
message CoinType {
|
||||
optional string coin_name = 1;
|
||||
optional string coin_shortcut = 2;
|
||||
optional uint32 address_type = 3 [default=0];
|
||||
optional uint64 maxfee_kb = 4;
|
||||
optional uint32 address_type_p2sh = 5 [default=5];
|
||||
optional string signed_message_header = 8;
|
||||
optional uint32 xpub_magic = 9 [default=76067358]; // default=0x0488b21e
|
||||
optional uint32 xprv_magic = 10 [default=76066276]; // default=0x0488ade4
|
||||
optional bool segwit = 11;
|
||||
optional uint32 forkid = 12;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of redeem script used in input
|
||||
* @used_in TxInputType
|
||||
*/
|
||||
message MultisigRedeemScriptType {
|
||||
repeated HDNodePathType pubkeys = 1; // pubkeys from multisig address (sorted lexicographically)
|
||||
repeated bytes signatures = 2; // existing signatures for partially signed input
|
||||
optional uint32 m = 3; // "m" from n, how many valid signatures is necessary for spending
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing transaction input
|
||||
* @used_in SimpleSignTx
|
||||
* @used_in TransactionType
|
||||
*/
|
||||
message TxInputType {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
required bytes prev_hash = 2; // hash of previous transaction output to spend by this input
|
||||
required uint32 prev_index = 3; // index of previous output to spend
|
||||
optional bytes script_sig = 4; // script signature, unset for tx to sign
|
||||
optional uint32 sequence = 5 [default=4294967295]; // sequence (default=0xffffffff)
|
||||
optional InputScriptType script_type = 6 [default=SPENDADDRESS]; // defines template of input script
|
||||
optional MultisigRedeemScriptType multisig = 7; // Filled if input is going to spend multisig tx
|
||||
optional uint64 amount = 8; // amount of previous transaction output (for segwit only)
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing transaction output
|
||||
* @used_in SimpleSignTx
|
||||
* @used_in TransactionType
|
||||
*/
|
||||
message TxOutputType {
|
||||
optional string address = 1; // target coin address in Base58 encoding
|
||||
repeated uint32 address_n = 2; // BIP-32 path to derive the key from master node; has higher priority than "address"
|
||||
required uint64 amount = 3; // amount to spend in satoshis
|
||||
required OutputScriptType script_type = 4; // output script type
|
||||
optional MultisigRedeemScriptType multisig = 5; // defines multisig address; script_type must be PAYTOMULTISIG
|
||||
optional bytes op_return_data = 6; // defines op_return data; script_type must be PAYTOOPRETURN, amount must be 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing compiled transaction output
|
||||
* @used_in TransactionType
|
||||
*/
|
||||
message TxOutputBinType {
|
||||
required uint64 amount = 1;
|
||||
required bytes script_pubkey = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing transaction
|
||||
* @used_in SimpleSignTx
|
||||
*/
|
||||
message TransactionType {
|
||||
optional uint32 version = 1;
|
||||
repeated TxInputType inputs = 2;
|
||||
repeated TxOutputBinType bin_outputs = 3;
|
||||
repeated TxOutputType outputs = 5;
|
||||
optional uint32 lock_time = 4;
|
||||
optional uint32 inputs_cnt = 6;
|
||||
optional uint32 outputs_cnt = 7;
|
||||
optional bytes extra_data = 8;
|
||||
optional uint32 extra_data_len = 9;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing request details
|
||||
* @used_in TxRequest
|
||||
*/
|
||||
message TxRequestDetailsType {
|
||||
optional uint32 request_index = 1; // device expects TxAck message from the computer
|
||||
optional bytes tx_hash = 2; // tx_hash of requested transaction
|
||||
optional uint32 extra_data_len = 3; // length of requested extra data
|
||||
optional uint32 extra_data_offset = 4; // offset of requested extra data
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing serialized data
|
||||
* @used_in TxRequest
|
||||
*/
|
||||
message TxRequestSerializedType {
|
||||
optional uint32 signature_index = 1; // 'signature' field contains signed input of this index
|
||||
optional bytes signature = 2; // signature of the signature_index input
|
||||
optional bytes serialized_tx = 3; // part of serialized and signed transaction
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure representing identity data
|
||||
* @used_in IdentityType
|
||||
*/
|
||||
message IdentityType {
|
||||
optional string proto = 1; // proto part of URI
|
||||
optional string user = 2; // user part of URI
|
||||
optional string host = 3; // host part of URI
|
||||
optional string port = 4; // port part of URI
|
||||
optional string path = 5; // path part of URI
|
||||
optional uint32 index = 6 [default=0]; // identity index
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/karalabe/hid"
|
||||
"github.com/karalabe/usb"
|
||||
)
|
||||
|
||||
// Maximum time between wallet health checks to detect USB unplugs.
|
||||
|
|
@ -77,8 +77,8 @@ type wallet struct {
|
|||
driver driver // Hardware implementation of the low level device operations
|
||||
url *accounts.URL // Textual URL uniquely identifying this wallet
|
||||
|
||||
info hid.DeviceInfo // Known USB device infos about the wallet
|
||||
device *hid.Device // USB device advertising itself as a hardware wallet
|
||||
info usb.DeviceInfo // Known USB device infos about the wallet
|
||||
device usb.Device // USB device advertising itself as a hardware wallet
|
||||
|
||||
accounts []accounts.Account // List of derive accounts pinned on the hardware wallet
|
||||
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
|
||||
|
|
|
|||
|
|
@ -133,10 +133,10 @@ var (
|
|||
|
||||
// Distros for which packages are created.
|
||||
// Note: vivid is unsupported because there is no golang-1.6 package for it.
|
||||
// Note: wily is unsupported because it was officially deprecated on lanchpad.
|
||||
// Note: yakkety is unsupported because it was officially deprecated on lanchpad.
|
||||
// Note: zesty is unsupported because it was officially deprecated on lanchpad.
|
||||
// Note: artful is unsupported because it was officially deprecated on lanchpad.
|
||||
// Note: wily is unsupported because it was officially deprecated on Launchpad.
|
||||
// Note: yakkety is unsupported because it was officially deprecated on Launchpad.
|
||||
// Note: zesty is unsupported because it was officially deprecated on Launchpad.
|
||||
// Note: artful is unsupported because it was officially deprecated on Launchpad.
|
||||
debDistros = []string{"trusty", "xenial", "bionic", "cosmic", "disco"}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
{{.Name}} ({{.VersionString}}) {{.Distro}}; urgency=low
|
||||
|
||||
* git build of {{.Env.Commit}}
|
||||
|
||||
-- {{.Author}} {{.Time}}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
Source: {{.Name}}
|
||||
Section: science
|
||||
Priority: extra
|
||||
Maintainer: {{.Author}}
|
||||
Build-Depends: debhelper (>= 8.0.0), golang-1.11
|
||||
Standards-Version: 3.9.5
|
||||
Homepage: https://ethereum.org
|
||||
Vcs-Git: git://github.com/ethereum/go-ethereum.git
|
||||
Vcs-Browser: https://github.com/ethereum/go-ethereum
|
||||
|
||||
{{range .Executables}}
|
||||
Package: {{$.ExeName .}}
|
||||
Conflicts: {{$.ExeConflicts .}}
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||
Built-Using: ${misc:Built-Using}
|
||||
Description: {{.Description}}
|
||||
{{.Description}}
|
||||
{{end}}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
Copyright 2018 The go-ethereum Authors
|
||||
|
||||
go-ethereum is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
go-ethereum is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
|
@ -1 +0,0 @@
|
|||
AUTHORS
|
||||
|
|
@ -1 +0,0 @@
|
|||
build/bin/{{.BinaryName}} usr/bin
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#!/usr/bin/make -f
|
||||
# -*- makefile -*-
|
||||
|
||||
# Uncomment this to turn on verbose mode.
|
||||
#export DH_VERBOSE=1
|
||||
|
||||
# Launchpad rejects Go's access to $HOME/.cache, use custom folder
|
||||
export GOCACHE=/tmp/go-build
|
||||
|
||||
override_dh_auto_build:
|
||||
build/env.sh /usr/lib/go-1.11/bin/go run build/ci.go install -git-commit={{.Env.Commit}} -git-branch={{.Env.Branch}} -git-tag={{.Env.Tag}} -buildnum={{.Env.Buildnum}} -pull-request={{.Env.IsPullRequest}}
|
||||
|
||||
override_dh_auto_test:
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
|
@ -11,8 +11,8 @@ Vcs-Browser: https://github.com/ethereum/go-ethereum
|
|||
Package: {{.Name}}
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}, {{.ExeList}}
|
||||
Description: Meta-package to install geth, swarm, and other tools
|
||||
Meta-package to install geth, swarm and other tools
|
||||
Description: Meta-package to install geth and other tools
|
||||
Meta-package to install geth and other tools
|
||||
|
||||
{{range .Executables}}
|
||||
Package: {{$.ExeName .}}
|
||||
|
|
|
|||
|
|
@ -72,8 +72,6 @@ var (
|
|||
"internal/jsre/deps",
|
||||
"log/",
|
||||
"common/bitutil/bitutil",
|
||||
// don't license generated files
|
||||
"contracts/chequebook/contract/code.go",
|
||||
}
|
||||
|
||||
// paths with this prefix are licensed as GPL. all other files are LGPL.
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ func main() {
|
|||
bins []string
|
||||
types []string
|
||||
)
|
||||
if *solFlag != "" || *vyFlag != "" || (*abiFlag == "-" && *pkgFlag == "") {
|
||||
if *solFlag != "" || *vyFlag != "" || *abiFlag == "-" {
|
||||
// Generate the list of types to exclude from binding
|
||||
exclude := make(map[string]bool)
|
||||
for _, kind := range strings.Split(*excFlag, ",") {
|
||||
|
|
@ -129,13 +129,8 @@ func main() {
|
|||
}
|
||||
} else {
|
||||
// Otherwise load up the ABI, optional bytecode and type name from the parameters
|
||||
var abi []byte
|
||||
var err error
|
||||
if *abiFlag == "-" {
|
||||
abi, err = ioutil.ReadAll(os.Stdin)
|
||||
} else {
|
||||
abi, err = ioutil.ReadFile(*abiFlag)
|
||||
}
|
||||
abi, err := ioutil.ReadFile(*abiFlag)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to read input ABI: %v\n", err)
|
||||
os.Exit(-1)
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ func printNotice(nodeKey *ecdsa.PublicKey, addr net.UDPAddr) {
|
|||
addr.IP = net.IP{127, 0, 0, 1}
|
||||
}
|
||||
n := enode.NewV4(nodeKey, addr.IP, 0, addr.Port)
|
||||
fmt.Println(n.String())
|
||||
fmt.Println(n.URLv4())
|
||||
fmt.Println("Note: you're using cmd/bootnode, a developer tool.")
|
||||
fmt.Println("We recommend using a regular node as bootstrap node for production deployments.")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
|
|
@ -193,6 +194,7 @@ func init() {
|
|||
chainIdFlag,
|
||||
utils.LightKDFFlag,
|
||||
utils.NoUSBFlag,
|
||||
utils.SmartCardDaemonPathFlag,
|
||||
utils.RPCListenAddrFlag,
|
||||
utils.RPCVirtualHostsFlag,
|
||||
utils.IPCDisabledFlag,
|
||||
|
|
@ -418,10 +420,11 @@ func signer(c *cli.Context) error {
|
|||
lightKdf = c.GlobalBool(utils.LightKDFFlag.Name)
|
||||
advanced = c.GlobalBool(advancedMode.Name)
|
||||
nousb = c.GlobalBool(utils.NoUSBFlag.Name)
|
||||
scpath = c.GlobalString(utils.SmartCardDaemonPathFlag.Name)
|
||||
)
|
||||
log.Info("Starting signer", "chainid", chainId, "keystore", ksLoc,
|
||||
"light-kdf", lightKdf, "advanced", advanced)
|
||||
am := core.StartClefAccountManager(ksLoc, nousb, lightKdf)
|
||||
am := core.StartClefAccountManager(ksLoc, nousb, lightKdf, scpath)
|
||||
apiImpl := core.NewSignerAPI(am, chainId, nousb, ui, db, advanced, pwStorage)
|
||||
|
||||
// Establish the bidirectional communication, by creating a new UI backend and registering
|
||||
|
|
@ -638,6 +641,10 @@ func testExternalUI(api *core.SignerAPI) {
|
|||
errs := make([]string, 0)
|
||||
|
||||
a := common.HexToAddress("0xdeadbeef000000000000000000000000deadbeef")
|
||||
addErr := func(errStr string) {
|
||||
log.Info("Test error", "error", errStr)
|
||||
errs = append(errs, errStr)
|
||||
}
|
||||
|
||||
queryUser := func(q string) string {
|
||||
resp, err := api.UI.OnInputRequired(core.UserInputRequest{
|
||||
|
|
@ -645,36 +652,39 @@ func testExternalUI(api *core.SignerAPI) {
|
|||
Prompt: q,
|
||||
})
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
addErr(err.Error())
|
||||
}
|
||||
return resp.Text
|
||||
}
|
||||
expectResponse := func(testcase, question, expect string) {
|
||||
if got := queryUser(question); got != expect {
|
||||
errs = append(errs, fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect))
|
||||
addErr(fmt.Sprintf("%s: got %v, expected %v", testcase, got, expect))
|
||||
}
|
||||
}
|
||||
expectApprove := func(testcase string, err error) {
|
||||
if err == nil || err == accounts.ErrUnknownAccount {
|
||||
return
|
||||
}
|
||||
errs = append(errs, fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error()))
|
||||
addErr(fmt.Sprintf("%v: expected no error, got %v", testcase, err.Error()))
|
||||
}
|
||||
expectDeny := func(testcase string, err error) {
|
||||
if err == nil || err != core.ErrRequestDenied {
|
||||
errs = append(errs, fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err))
|
||||
addErr(fmt.Sprintf("%v: expected ErrRequestDenied, got %v", testcase, err))
|
||||
}
|
||||
}
|
||||
|
||||
var delay = 1 * time.Second
|
||||
// Test display of info and error
|
||||
{
|
||||
api.UI.ShowInfo("If you see this message, enter 'yes' to next question")
|
||||
time.Sleep(delay)
|
||||
expectResponse("showinfo", "Did you see the message? [yes/no]", "yes")
|
||||
api.UI.ShowError("If you see this message, enter 'yes' to the next question")
|
||||
time.Sleep(delay)
|
||||
expectResponse("showerror", "Did you see the message? [yes/no]", "yes")
|
||||
}
|
||||
{ // Sign data test - clique header
|
||||
api.UI.ShowInfo("Please approve the next request for signing a clique header")
|
||||
time.Sleep(delay)
|
||||
cliqueHeader := types.Header{
|
||||
common.HexToHash("0000H45H"),
|
||||
common.HexToHash("0000H45H"),
|
||||
|
|
@ -700,14 +710,27 @@ func testExternalUI(api *core.SignerAPI) {
|
|||
_, err = api.SignData(ctx, accounts.MimetypeClique, *addr, hexutil.Encode(cliqueRlp))
|
||||
expectApprove("signdata - clique header", err)
|
||||
}
|
||||
{ // Sign data test - typed data
|
||||
api.UI.ShowInfo("Please approve the next request for signing EIP-712 typed data")
|
||||
time.Sleep(delay)
|
||||
addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
|
||||
data := `{"types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Person":[{"name":"name","type":"string"},{"name":"test","type":"uint8"},{"name":"wallet","type":"address"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person"},{"name":"contents","type":"string"}]},"primaryType":"Mail","domain":{"name":"Ether Mail","version":"1","chainId":"1","verifyingContract":"0xCCCcccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"},"message":{"from":{"name":"Cow","test":"3","wallet":"0xcD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"},"to":{"name":"Bob","wallet":"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB","test":"2"},"contents":"Hello, Bob!"}}`
|
||||
//_, err := api.SignData(ctx, accounts.MimetypeTypedData, *addr, hexutil.Encode([]byte(data)))
|
||||
var typedData core.TypedData
|
||||
err := json.Unmarshal([]byte(data), &typedData)
|
||||
_, err = api.SignTypedData(ctx, *addr, typedData)
|
||||
expectApprove("sign 712 typed data", err)
|
||||
}
|
||||
{ // Sign data test - plain text
|
||||
api.UI.ShowInfo("Please approve the next request for signing text")
|
||||
time.Sleep(delay)
|
||||
addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
|
||||
_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
|
||||
expectApprove("signdata - text", err)
|
||||
}
|
||||
{ // Sign data test - plain text reject
|
||||
api.UI.ShowInfo("Please deny the next request for signing text")
|
||||
time.Sleep(delay)
|
||||
addr, _ := common.NewMixedcaseAddressFromString("0x0011223344556677889900112233445566778899")
|
||||
_, err := api.SignData(ctx, accounts.MimetypeTextPlain, *addr, hexutil.Encode([]byte("hello world")))
|
||||
expectDeny("signdata - text", err)
|
||||
|
|
@ -715,6 +738,7 @@ func testExternalUI(api *core.SignerAPI) {
|
|||
{ // Sign transaction
|
||||
|
||||
api.UI.ShowInfo("Please reject next transaction")
|
||||
time.Sleep(delay)
|
||||
data := hexutil.Bytes([]byte{})
|
||||
to := common.NewMixedcaseAddress(a)
|
||||
tx := core.SendTxArgs{
|
||||
|
|
@ -733,16 +757,19 @@ func testExternalUI(api *core.SignerAPI) {
|
|||
}
|
||||
{ // Listing
|
||||
api.UI.ShowInfo("Please reject listing-request")
|
||||
time.Sleep(delay)
|
||||
_, err := api.List(ctx)
|
||||
expectDeny("list", err)
|
||||
}
|
||||
{ // Import
|
||||
api.UI.ShowInfo("Please reject new account-request")
|
||||
time.Sleep(delay)
|
||||
_, err := api.New(ctx)
|
||||
expectDeny("newaccount", err)
|
||||
}
|
||||
{ // Metadata
|
||||
api.UI.ShowInfo("Please check if you see the Origin in next listing (approve or deny)")
|
||||
time.Sleep(delay)
|
||||
api.List(context.WithValue(ctx, "Origin", "origin.com"))
|
||||
expectResponse("metadata - origin", "Did you see origin (origin.com)? [yes/no] ", "yes")
|
||||
}
|
||||
|
|
|
|||
166
cmd/devp2p/discv4cmd.go
Normal file
166
cmd/devp2p/discv4cmd.go
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
discv4Command = cli.Command{
|
||||
Name: "discv4",
|
||||
Usage: "Node Discovery v4 tools",
|
||||
Subcommands: []cli.Command{
|
||||
discv4PingCommand,
|
||||
discv4RequestRecordCommand,
|
||||
discv4ResolveCommand,
|
||||
},
|
||||
}
|
||||
discv4PingCommand = cli.Command{
|
||||
Name: "ping",
|
||||
Usage: "Sends ping to a node",
|
||||
Action: discv4Ping,
|
||||
}
|
||||
discv4RequestRecordCommand = cli.Command{
|
||||
Name: "requestenr",
|
||||
Usage: "Requests a node record using EIP-868 enrRequest",
|
||||
Action: discv4RequestRecord,
|
||||
}
|
||||
discv4ResolveCommand = cli.Command{
|
||||
Name: "resolve",
|
||||
Usage: "Finds a node in the DHT",
|
||||
Action: discv4Resolve,
|
||||
Flags: []cli.Flag{bootnodesFlag},
|
||||
}
|
||||
)
|
||||
|
||||
var bootnodesFlag = cli.StringFlag{
|
||||
Name: "bootnodes",
|
||||
Usage: "Comma separated nodes used for bootstrapping",
|
||||
}
|
||||
|
||||
func discv4Ping(ctx *cli.Context) error {
|
||||
n, disc, err := getNodeArgAndStartV4(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer disc.Close()
|
||||
|
||||
start := time.Now()
|
||||
if err := disc.Ping(n); err != nil {
|
||||
return fmt.Errorf("node didn't respond: %v", err)
|
||||
}
|
||||
fmt.Printf("node responded to ping (RTT %v).\n", time.Since(start))
|
||||
return nil
|
||||
}
|
||||
|
||||
func discv4RequestRecord(ctx *cli.Context) error {
|
||||
n, disc, err := getNodeArgAndStartV4(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer disc.Close()
|
||||
|
||||
respN, err := disc.RequestENR(n)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't retrieve record: %v", err)
|
||||
}
|
||||
fmt.Println(respN.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func discv4Resolve(ctx *cli.Context) error {
|
||||
n, disc, err := getNodeArgAndStartV4(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer disc.Close()
|
||||
|
||||
fmt.Println(disc.Resolve(n).String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func getNodeArgAndStartV4(ctx *cli.Context) (*enode.Node, *discover.UDPv4, error) {
|
||||
if ctx.NArg() != 1 {
|
||||
return nil, nil, fmt.Errorf("missing node as command-line argument")
|
||||
}
|
||||
n, err := parseNode(ctx.Args()[0])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var bootnodes []*enode.Node
|
||||
if commandHasFlag(ctx, bootnodesFlag) {
|
||||
bootnodes, err = parseBootnodes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
disc, err := startV4(bootnodes)
|
||||
return n, disc, err
|
||||
}
|
||||
|
||||
func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) {
|
||||
s := params.RinkebyBootnodes
|
||||
if ctx.IsSet(bootnodesFlag.Name) {
|
||||
s = strings.Split(ctx.String(bootnodesFlag.Name), ",")
|
||||
}
|
||||
nodes := make([]*enode.Node, len(s))
|
||||
var err error
|
||||
for i, record := range s {
|
||||
nodes[i], err = parseNode(record)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid bootstrap node: %v", err)
|
||||
}
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// commandHasFlag returns true if the current command supports the given flag.
|
||||
func commandHasFlag(ctx *cli.Context, flag cli.Flag) bool {
|
||||
flags := ctx.FlagNames()
|
||||
sort.Strings(flags)
|
||||
i := sort.SearchStrings(flags, flag.GetName())
|
||||
return i != len(flags) && flags[i] == flag.GetName()
|
||||
}
|
||||
|
||||
// startV4 starts an ephemeral discovery V4 node.
|
||||
func startV4(bootnodes []*enode.Node) (*discover.UDPv4, error) {
|
||||
var cfg discover.Config
|
||||
cfg.Bootnodes = bootnodes
|
||||
cfg.PrivateKey, _ = crypto.GenerateKey()
|
||||
db, _ := enode.OpenDB("")
|
||||
ln := enode.NewLocalNode(db, cfg.PrivateKey)
|
||||
|
||||
socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{0, 0, 0, 0}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr := socket.LocalAddr().(*net.UDPAddr)
|
||||
ln.SetFallbackIP(net.IP{127, 0, 0, 1})
|
||||
ln.SetFallbackUDP(addr.Port)
|
||||
return discover.ListenUDP(socket, ln, cfg)
|
||||
}
|
||||
198
cmd/devp2p/enrcmd.go
Normal file
198
cmd/devp2p/enrcmd.go
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var enrdumpCommand = cli.Command{
|
||||
Name: "enrdump",
|
||||
Usage: "Pretty-prints node records",
|
||||
Action: enrdump,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "file"},
|
||||
},
|
||||
}
|
||||
|
||||
func enrdump(ctx *cli.Context) error {
|
||||
var source string
|
||||
if file := ctx.String("file"); file != "" {
|
||||
if ctx.NArg() != 0 {
|
||||
return fmt.Errorf("can't dump record from command-line argument in -file mode")
|
||||
}
|
||||
var b []byte
|
||||
var err error
|
||||
if file == "-" {
|
||||
b, err = ioutil.ReadAll(os.Stdin)
|
||||
} else {
|
||||
b, err = ioutil.ReadFile(file)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source = string(b)
|
||||
} else if ctx.NArg() == 1 {
|
||||
source = ctx.Args()[0]
|
||||
} else {
|
||||
return fmt.Errorf("need record as argument")
|
||||
}
|
||||
|
||||
r, err := parseRecord(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("INVALID: %v", err)
|
||||
}
|
||||
fmt.Print(dumpRecord(r))
|
||||
return nil
|
||||
}
|
||||
|
||||
// dumpRecord creates a human-readable description of the given node record.
|
||||
func dumpRecord(r *enr.Record) string {
|
||||
out := new(bytes.Buffer)
|
||||
if n, err := enode.New(enode.ValidSchemes, r); err != nil {
|
||||
fmt.Fprintf(out, "INVALID: %v\n", err)
|
||||
} else {
|
||||
fmt.Fprintf(out, "Node ID: %v\n", n.ID())
|
||||
}
|
||||
kv := r.AppendElements(nil)[1:]
|
||||
fmt.Fprintf(out, "Record has sequence number %d and %d key/value pairs.\n", r.Seq(), len(kv)/2)
|
||||
fmt.Fprint(out, dumpRecordKV(kv, 2))
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func dumpRecordKV(kv []interface{}, indent int) string {
|
||||
// Determine the longest key name for alignment.
|
||||
var out string
|
||||
var longestKey = 0
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
key := kv[i].(string)
|
||||
if len(key) > longestKey {
|
||||
longestKey = len(key)
|
||||
}
|
||||
}
|
||||
// Print the keys, invoking formatters for known keys.
|
||||
for i := 0; i < len(kv); i += 2 {
|
||||
key := kv[i].(string)
|
||||
val := kv[i+1].(rlp.RawValue)
|
||||
pad := longestKey - len(key)
|
||||
out += strings.Repeat(" ", indent) + strconv.Quote(key) + strings.Repeat(" ", pad+1)
|
||||
formatter := attrFormatters[key]
|
||||
if formatter == nil {
|
||||
formatter = formatAttrRaw
|
||||
}
|
||||
fmtval, ok := formatter(val)
|
||||
if ok {
|
||||
out += fmtval + "\n"
|
||||
} else {
|
||||
out += hex.EncodeToString(val) + " (!)\n"
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// parseNode parses a node record and verifies its signature.
|
||||
func parseNode(source string) (*enode.Node, error) {
|
||||
if strings.HasPrefix(source, "enode://") {
|
||||
return enode.ParseV4(source)
|
||||
}
|
||||
r, err := parseRecord(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enode.New(enode.ValidSchemes, r)
|
||||
}
|
||||
|
||||
// parseRecord parses a node record from hex, base64, or raw binary input.
|
||||
func parseRecord(source string) (*enr.Record, error) {
|
||||
bin := []byte(source)
|
||||
if d, ok := decodeRecordHex(bytes.TrimSpace(bin)); ok {
|
||||
bin = d
|
||||
} else if d, ok := decodeRecordBase64(bytes.TrimSpace(bin)); ok {
|
||||
bin = d
|
||||
}
|
||||
var r enr.Record
|
||||
err := rlp.DecodeBytes(bin, &r)
|
||||
return &r, err
|
||||
}
|
||||
|
||||
func decodeRecordHex(b []byte) ([]byte, bool) {
|
||||
if bytes.HasPrefix(b, []byte("0x")) {
|
||||
b = b[2:]
|
||||
}
|
||||
dec := make([]byte, hex.DecodedLen(len(b)))
|
||||
_, err := hex.Decode(dec, b)
|
||||
return dec, err == nil
|
||||
}
|
||||
|
||||
func decodeRecordBase64(b []byte) ([]byte, bool) {
|
||||
if bytes.HasPrefix(b, []byte("enr:")) {
|
||||
b = b[4:]
|
||||
}
|
||||
dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(b)))
|
||||
n, err := base64.RawURLEncoding.Decode(dec, b)
|
||||
return dec[:n], err == nil
|
||||
}
|
||||
|
||||
// attrFormatters contains formatting functions for well-known ENR keys.
|
||||
var attrFormatters = map[string]func(rlp.RawValue) (string, bool){
|
||||
"id": formatAttrString,
|
||||
"ip": formatAttrIP,
|
||||
"ip6": formatAttrIP,
|
||||
"tcp": formatAttrUint,
|
||||
"tcp6": formatAttrUint,
|
||||
"udp": formatAttrUint,
|
||||
"udp6": formatAttrUint,
|
||||
}
|
||||
|
||||
func formatAttrRaw(v rlp.RawValue) (string, bool) {
|
||||
s := hex.EncodeToString(v)
|
||||
return s, true
|
||||
}
|
||||
|
||||
func formatAttrString(v rlp.RawValue) (string, bool) {
|
||||
content, _, err := rlp.SplitString(v)
|
||||
return strconv.Quote(string(content)), err == nil
|
||||
}
|
||||
|
||||
func formatAttrIP(v rlp.RawValue) (string, bool) {
|
||||
content, _, err := rlp.SplitString(v)
|
||||
if err != nil || len(content) != 4 && len(content) != 6 {
|
||||
return "", false
|
||||
}
|
||||
return net.IP(content).String(), true
|
||||
}
|
||||
|
||||
func formatAttrUint(v rlp.RawValue) (string, bool) {
|
||||
var x uint64
|
||||
if err := rlp.DecodeBytes(v, &x); err != nil {
|
||||
return "", false
|
||||
}
|
||||
return strconv.FormatUint(x, 10), true
|
||||
}
|
||||
68
cmd/devp2p/main.go
Normal file
68
cmd/devp2p/main.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
// Git information set by linker when building with ci.go.
|
||||
gitCommit string
|
||||
gitDate string
|
||||
app = &cli.App{
|
||||
Name: filepath.Base(os.Args[0]),
|
||||
Usage: "go-ethereum devp2p tool",
|
||||
Version: params.VersionWithCommit(gitCommit, gitDate),
|
||||
Writer: os.Stdout,
|
||||
HideVersion: true,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Set up the CLI app.
|
||||
app.Flags = append(app.Flags, debug.Flags...)
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
return debug.Setup(ctx, "")
|
||||
}
|
||||
app.After = func(ctx *cli.Context) error {
|
||||
debug.Exit()
|
||||
return nil
|
||||
}
|
||||
app.CommandNotFound = func(ctx *cli.Context, cmd string) {
|
||||
fmt.Fprintf(os.Stderr, "No such command: %s\n", cmd)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Add subcommands.
|
||||
app.Commands = []cli.Command{
|
||||
enrdumpCommand,
|
||||
discv4Command,
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -260,7 +260,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
|
|||
return nil, err
|
||||
}
|
||||
for _, boot := range enodes {
|
||||
old, err := enode.ParseV4(boot.String())
|
||||
old, err := enode.Parse(enode.ValidSchemes, boot.String())
|
||||
if err == nil {
|
||||
stack.Server().AddPeer(old)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ var (
|
|||
utils.KeyStoreDirFlag,
|
||||
utils.ExternalSignerFlag,
|
||||
utils.NoUSBFlag,
|
||||
utils.SmartCardDaemonPathFlag,
|
||||
utils.DashboardEnabledFlag,
|
||||
utils.DashboardAddrFlag,
|
||||
utils.DashboardPortFlag,
|
||||
|
|
@ -219,6 +220,8 @@ func init() {
|
|||
licenseCommand,
|
||||
// See config.go
|
||||
dumpConfigCommand,
|
||||
// See retesteth.go
|
||||
retestethCommand,
|
||||
}
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
|
||||
|
|
|
|||
888
cmd/geth/retesteth.go
Normal file
888
cmd/geth/retesteth.go
Normal file
|
|
@ -0,0 +1,888 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/trie"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
rpcPortFlag = cli.IntFlag{
|
||||
Name: "rpcport",
|
||||
Usage: "HTTP-RPC server listening port",
|
||||
Value: node.DefaultHTTPPort,
|
||||
}
|
||||
retestethCommand = cli.Command{
|
||||
Action: utils.MigrateFlags(retesteth),
|
||||
Name: "retesteth",
|
||||
Usage: "Launches geth in retesteth mode",
|
||||
ArgsUsage: "",
|
||||
Flags: []cli.Flag{rpcPortFlag},
|
||||
Category: "MISCELLANEOUS COMMANDS",
|
||||
Description: `Launches geth in retesteth mode (no database, no network, only retesteth RPC interface)`,
|
||||
}
|
||||
)
|
||||
|
||||
type RetestethTestAPI interface {
|
||||
SetChainParams(ctx context.Context, chainParams ChainParams) (bool, error)
|
||||
MineBlocks(ctx context.Context, number uint64) (bool, error)
|
||||
ModifyTimestamp(ctx context.Context, interval uint64) (bool, error)
|
||||
ImportRawBlock(ctx context.Context, rawBlock hexutil.Bytes) (common.Hash, error)
|
||||
RewindToBlock(ctx context.Context, number uint64) (bool, error)
|
||||
GetLogHash(ctx context.Context, txHash common.Hash) (common.Hash, error)
|
||||
}
|
||||
|
||||
type RetestethEthAPI interface {
|
||||
SendRawTransaction(ctx context.Context, rawTx hexutil.Bytes) (common.Hash, error)
|
||||
BlockNumber(ctx context.Context) (uint64, error)
|
||||
GetBlockByNumber(ctx context.Context, blockNr math.HexOrDecimal64, fullTx bool) (map[string]interface{}, error)
|
||||
GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error)
|
||||
GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error)
|
||||
GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error)
|
||||
}
|
||||
|
||||
type RetestethDebugAPI interface {
|
||||
AccountRangeAt(ctx context.Context,
|
||||
blockHashOrNumber *math.HexOrDecimal256, txIndex uint64,
|
||||
addressHash *math.HexOrDecimal256, maxResults uint64,
|
||||
) (AccountRangeResult, error)
|
||||
StorageRangeAt(ctx context.Context,
|
||||
blockHashOrNumber *math.HexOrDecimal256, txIndex uint64,
|
||||
address common.Address,
|
||||
begin *math.HexOrDecimal256, maxResults uint64,
|
||||
) (StorageRangeResult, error)
|
||||
}
|
||||
|
||||
type RetestWeb3API interface {
|
||||
ClientVersion(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
type RetestethAPI struct {
|
||||
ethDb ethdb.Database
|
||||
db state.Database
|
||||
chainConfig *params.ChainConfig
|
||||
author common.Address
|
||||
extraData []byte
|
||||
genesisHash common.Hash
|
||||
engine *NoRewardEngine
|
||||
blockchain *core.BlockChain
|
||||
blockNumber uint64
|
||||
txMap map[common.Address]map[uint64]*types.Transaction // Sender -> Nonce -> Transaction
|
||||
txSenders map[common.Address]struct{} // Set of transaction senders
|
||||
blockInterval uint64
|
||||
}
|
||||
|
||||
type ChainParams struct {
|
||||
SealEngine string `json:"sealEngine"`
|
||||
Params CParamsParams `json:"params"`
|
||||
Genesis CParamsGenesis `json:"genesis"`
|
||||
Accounts map[common.Address]CParamsAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
type CParamsParams struct {
|
||||
AccountStartNonce math.HexOrDecimal64 `json:"accountStartNonce"`
|
||||
HomesteadForkBlock *math.HexOrDecimal64 `json:"homesteadForkBlock"`
|
||||
EIP150ForkBlock *math.HexOrDecimal64 `json:"EIP150ForkBlock"`
|
||||
EIP158ForkBlock *math.HexOrDecimal64 `json:"EIP158ForkBlock"`
|
||||
DaoHardforkBlock *math.HexOrDecimal64 `json:"daoHardforkBlock"`
|
||||
ByzantiumForkBlock *math.HexOrDecimal64 `json:"byzantiumForkBlock"`
|
||||
ConstantinopleForkBlock *math.HexOrDecimal64 `json:"constantinopleForkBlock"`
|
||||
ConstantinopleFixForkBlock *math.HexOrDecimal64 `json:"constantinopleFixForkBlock"`
|
||||
ChainID *math.HexOrDecimal256 `json:"chainID"`
|
||||
MaximumExtraDataSize math.HexOrDecimal64 `json:"maximumExtraDataSize"`
|
||||
TieBreakingGas bool `json:"tieBreakingGas"`
|
||||
MinGasLimit math.HexOrDecimal64 `json:"minGasLimit"`
|
||||
MaxGasLimit math.HexOrDecimal64 `json:"maxGasLimit"`
|
||||
GasLimitBoundDivisor math.HexOrDecimal64 `json:"gasLimitBoundDivisor"`
|
||||
MinimumDifficulty math.HexOrDecimal256 `json:"minimumDifficulty"`
|
||||
DifficultyBoundDivisor math.HexOrDecimal256 `json:"difficultyBoundDivisor"`
|
||||
DurationLimit math.HexOrDecimal256 `json:"durationLimit"`
|
||||
BlockReward math.HexOrDecimal256 `json:"blockReward"`
|
||||
NetworkID math.HexOrDecimal256 `json:"networkID"`
|
||||
}
|
||||
|
||||
type CParamsGenesis struct {
|
||||
Nonce math.HexOrDecimal64 `json:"nonce"`
|
||||
Difficulty *math.HexOrDecimal256 `json:"difficulty"`
|
||||
MixHash *math.HexOrDecimal256 `json:"mixHash"`
|
||||
Author common.Address `json:"author"`
|
||||
Timestamp math.HexOrDecimal64 `json:"timestamp"`
|
||||
ParentHash common.Hash `json:"parentHash"`
|
||||
ExtraData hexutil.Bytes `json:"extraData"`
|
||||
GasLimit math.HexOrDecimal64 `json:"gasLimit"`
|
||||
}
|
||||
|
||||
type CParamsAccount struct {
|
||||
Balance *math.HexOrDecimal256 `json:"balance"`
|
||||
Precompiled *CPAccountPrecompiled `json:"precompiled"`
|
||||
Code hexutil.Bytes `json:"code"`
|
||||
Storage map[string]string `json:"storage"`
|
||||
Nonce *math.HexOrDecimal64 `json:"nonce"`
|
||||
}
|
||||
|
||||
type CPAccountPrecompiled struct {
|
||||
Name string `json:"name"`
|
||||
StartingBlock math.HexOrDecimal64 `json:"startingBlock"`
|
||||
Linear *CPAPrecompiledLinear `json:"linear"`
|
||||
}
|
||||
|
||||
type CPAPrecompiledLinear struct {
|
||||
Base uint64 `json:"base"`
|
||||
Word uint64 `json:"word"`
|
||||
}
|
||||
|
||||
type AccountRangeResult struct {
|
||||
AddressMap map[common.Hash]common.Address `json:"addressMap"`
|
||||
NextKey common.Hash `json:"nextKey"`
|
||||
}
|
||||
|
||||
type StorageRangeResult struct {
|
||||
Complete bool `json:"complete"`
|
||||
Storage map[common.Hash]SRItem `json:"storage"`
|
||||
}
|
||||
|
||||
type SRItem struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type NoRewardEngine struct {
|
||||
inner consensus.Engine
|
||||
rewardsOn bool
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) Author(header *types.Header) (common.Address, error) {
|
||||
return e.inner.Author(header)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) VerifyHeader(chain consensus.ChainReader, header *types.Header, seal bool) error {
|
||||
return e.inner.VerifyHeader(chain, header, seal)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
|
||||
return e.inner.VerifyHeaders(chain, headers, seals)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
|
||||
return e.inner.VerifyUncles(chain, block)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
||||
return e.inner.VerifySeal(chain, header)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
||||
return e.inner.Prepare(chain, header)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
|
||||
// Simply touch miner and uncle coinbase accounts
|
||||
reward := big.NewInt(0)
|
||||
for _, uncle := range uncles {
|
||||
state.AddBalance(uncle.Coinbase, reward)
|
||||
}
|
||||
state.AddBalance(header.Coinbase, reward)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) Finalize(chain consensus.ChainReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction,
|
||||
uncles []*types.Header) {
|
||||
if e.rewardsOn {
|
||||
e.inner.Finalize(chain, header, statedb, txs, uncles)
|
||||
} else {
|
||||
e.accumulateRewards(chain.Config(), statedb, header, uncles)
|
||||
header.Root = statedb.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
}
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, statedb *state.StateDB, txs []*types.Transaction,
|
||||
uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
|
||||
if e.rewardsOn {
|
||||
return e.inner.FinalizeAndAssemble(chain, header, statedb, txs, uncles, receipts)
|
||||
} else {
|
||||
e.accumulateRewards(chain.Config(), statedb, header, uncles)
|
||||
header.Root = statedb.IntermediateRoot(chain.Config().IsEIP158(header.Number))
|
||||
|
||||
// Header seems complete, assemble into a block and return
|
||||
return types.NewBlock(header, txs, uncles, receipts), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) Seal(chain consensus.ChainReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
|
||||
return e.inner.Seal(chain, block, results, stop)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) SealHash(header *types.Header) common.Hash {
|
||||
return e.inner.SealHash(header)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
|
||||
return e.inner.CalcDifficulty(chain, time, parent)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) APIs(chain consensus.ChainReader) []rpc.API {
|
||||
return e.inner.APIs(chain)
|
||||
}
|
||||
|
||||
func (e *NoRewardEngine) Close() error {
|
||||
return e.inner.Close()
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainParams) (bool, error) {
|
||||
// Clean up
|
||||
if api.blockchain != nil {
|
||||
api.blockchain.Stop()
|
||||
}
|
||||
if api.engine != nil {
|
||||
api.engine.Close()
|
||||
}
|
||||
if api.ethDb != nil {
|
||||
api.ethDb.Close()
|
||||
}
|
||||
ethDb := rawdb.NewMemoryDatabase()
|
||||
accounts := make(core.GenesisAlloc)
|
||||
for address, account := range chainParams.Accounts {
|
||||
balance := big.NewInt(0)
|
||||
if account.Balance != nil {
|
||||
balance.Set((*big.Int)(account.Balance))
|
||||
}
|
||||
var nonce uint64
|
||||
if account.Nonce != nil {
|
||||
nonce = uint64(*account.Nonce)
|
||||
}
|
||||
if account.Precompiled == nil || account.Balance != nil {
|
||||
storage := make(map[common.Hash]common.Hash)
|
||||
for k, v := range account.Storage {
|
||||
storage[common.HexToHash(k)] = common.HexToHash(v)
|
||||
}
|
||||
accounts[address] = core.GenesisAccount{
|
||||
Balance: balance,
|
||||
Code: account.Code,
|
||||
Nonce: nonce,
|
||||
Storage: storage,
|
||||
}
|
||||
}
|
||||
}
|
||||
chainId := big.NewInt(1)
|
||||
if chainParams.Params.ChainID != nil {
|
||||
chainId.Set((*big.Int)(chainParams.Params.ChainID))
|
||||
}
|
||||
var (
|
||||
homesteadBlock *big.Int
|
||||
daoForkBlock *big.Int
|
||||
eip150Block *big.Int
|
||||
eip155Block *big.Int
|
||||
eip158Block *big.Int
|
||||
byzantiumBlock *big.Int
|
||||
constantinopleBlock *big.Int
|
||||
petersburgBlock *big.Int
|
||||
)
|
||||
if chainParams.Params.HomesteadForkBlock != nil {
|
||||
homesteadBlock = big.NewInt(int64(*chainParams.Params.HomesteadForkBlock))
|
||||
}
|
||||
if chainParams.Params.DaoHardforkBlock != nil {
|
||||
daoForkBlock = big.NewInt(int64(*chainParams.Params.DaoHardforkBlock))
|
||||
}
|
||||
if chainParams.Params.EIP150ForkBlock != nil {
|
||||
eip150Block = big.NewInt(int64(*chainParams.Params.EIP150ForkBlock))
|
||||
}
|
||||
if chainParams.Params.EIP158ForkBlock != nil {
|
||||
eip158Block = big.NewInt(int64(*chainParams.Params.EIP158ForkBlock))
|
||||
eip155Block = eip158Block
|
||||
}
|
||||
if chainParams.Params.ByzantiumForkBlock != nil {
|
||||
byzantiumBlock = big.NewInt(int64(*chainParams.Params.ByzantiumForkBlock))
|
||||
}
|
||||
if chainParams.Params.ConstantinopleForkBlock != nil {
|
||||
constantinopleBlock = big.NewInt(int64(*chainParams.Params.ConstantinopleForkBlock))
|
||||
}
|
||||
if chainParams.Params.ConstantinopleFixForkBlock != nil {
|
||||
petersburgBlock = big.NewInt(int64(*chainParams.Params.ConstantinopleFixForkBlock))
|
||||
}
|
||||
if constantinopleBlock != nil && petersburgBlock == nil {
|
||||
petersburgBlock = big.NewInt(100000000000)
|
||||
}
|
||||
genesis := &core.Genesis{
|
||||
Config: ¶ms.ChainConfig{
|
||||
ChainID: chainId,
|
||||
HomesteadBlock: homesteadBlock,
|
||||
DAOForkBlock: daoForkBlock,
|
||||
DAOForkSupport: false,
|
||||
EIP150Block: eip150Block,
|
||||
EIP155Block: eip155Block,
|
||||
EIP158Block: eip158Block,
|
||||
ByzantiumBlock: byzantiumBlock,
|
||||
ConstantinopleBlock: constantinopleBlock,
|
||||
PetersburgBlock: petersburgBlock,
|
||||
},
|
||||
Nonce: uint64(chainParams.Genesis.Nonce),
|
||||
Timestamp: uint64(chainParams.Genesis.Timestamp),
|
||||
ExtraData: chainParams.Genesis.ExtraData,
|
||||
GasLimit: uint64(chainParams.Genesis.GasLimit),
|
||||
Difficulty: big.NewInt(0).Set((*big.Int)(chainParams.Genesis.Difficulty)),
|
||||
Mixhash: common.BigToHash((*big.Int)(chainParams.Genesis.MixHash)),
|
||||
Coinbase: chainParams.Genesis.Author,
|
||||
ParentHash: chainParams.Genesis.ParentHash,
|
||||
Alloc: accounts,
|
||||
}
|
||||
chainConfig, genesisHash, err := core.SetupGenesisBlock(ethDb, genesis)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
fmt.Printf("Chain config: %v\n", chainConfig)
|
||||
|
||||
var inner consensus.Engine
|
||||
switch chainParams.SealEngine {
|
||||
case "NoProof", "NoReward":
|
||||
inner = ethash.NewFaker()
|
||||
case "Ethash":
|
||||
inner = ethash.New(ethash.Config{
|
||||
CacheDir: "ethash",
|
||||
CachesInMem: 2,
|
||||
CachesOnDisk: 3,
|
||||
DatasetsInMem: 1,
|
||||
DatasetsOnDisk: 2,
|
||||
}, nil, false)
|
||||
default:
|
||||
return false, fmt.Errorf("unrecognised seal engine: %s", chainParams.SealEngine)
|
||||
}
|
||||
engine := &NoRewardEngine{inner: inner, rewardsOn: chainParams.SealEngine != "NoReward"}
|
||||
|
||||
blockchain, err := core.NewBlockChain(ethDb, nil, chainConfig, engine, vm.Config{}, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
api.chainConfig = chainConfig
|
||||
api.genesisHash = genesisHash
|
||||
api.author = chainParams.Genesis.Author
|
||||
api.extraData = chainParams.Genesis.ExtraData
|
||||
api.ethDb = ethDb
|
||||
api.engine = engine
|
||||
api.blockchain = blockchain
|
||||
api.db = state.NewDatabase(api.ethDb)
|
||||
api.blockNumber = 0
|
||||
api.txMap = make(map[common.Address]map[uint64]*types.Transaction)
|
||||
api.txSenders = make(map[common.Address]struct{})
|
||||
api.blockInterval = 0
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) SendRawTransaction(ctx context.Context, rawTx hexutil.Bytes) (common.Hash, error) {
|
||||
tx := new(types.Transaction)
|
||||
if err := rlp.DecodeBytes(rawTx, tx); err != nil {
|
||||
// Return nil is not by mistake - some tests include sending transaction where gasLimit overflows uint64
|
||||
return common.Hash{}, nil
|
||||
}
|
||||
signer := types.MakeSigner(api.chainConfig, big.NewInt(int64(api.blockNumber)))
|
||||
sender, err := types.Sender(signer, tx)
|
||||
if err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
if nonceMap, ok := api.txMap[sender]; ok {
|
||||
nonceMap[tx.Nonce()] = tx
|
||||
} else {
|
||||
nonceMap = make(map[uint64]*types.Transaction)
|
||||
nonceMap[tx.Nonce()] = tx
|
||||
api.txMap[sender] = nonceMap
|
||||
}
|
||||
api.txSenders[sender] = struct{}{}
|
||||
return tx.Hash(), nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) MineBlocks(ctx context.Context, number uint64) (bool, error) {
|
||||
for i := 0; i < int(number); i++ {
|
||||
if err := api.mineBlock(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
fmt.Printf("Mined %d blocks\n", number)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) mineBlock() error {
|
||||
parentHash := rawdb.ReadCanonicalHash(api.ethDb, api.blockNumber)
|
||||
parent := rawdb.ReadBlock(api.ethDb, parentHash, api.blockNumber)
|
||||
var timestamp uint64
|
||||
if api.blockInterval == 0 {
|
||||
timestamp = uint64(time.Now().Unix())
|
||||
} else {
|
||||
timestamp = parent.Time() + api.blockInterval
|
||||
}
|
||||
gasLimit := core.CalcGasLimit(parent, 9223372036854775807, 9223372036854775807)
|
||||
header := &types.Header{
|
||||
ParentHash: parent.Hash(),
|
||||
Number: big.NewInt(int64(api.blockNumber + 1)),
|
||||
GasLimit: gasLimit,
|
||||
Extra: api.extraData,
|
||||
Time: timestamp,
|
||||
}
|
||||
header.Coinbase = api.author
|
||||
if api.engine != nil {
|
||||
api.engine.Prepare(api.blockchain, header)
|
||||
}
|
||||
// If we are care about TheDAO hard-fork check whether to override the extra-data or not
|
||||
if daoBlock := api.chainConfig.DAOForkBlock; daoBlock != nil {
|
||||
// Check whether the block is among the fork extra-override range
|
||||
limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
|
||||
if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 {
|
||||
// Depending whether we support or oppose the fork, override differently
|
||||
if api.chainConfig.DAOForkSupport {
|
||||
header.Extra = common.CopyBytes(params.DAOForkBlockExtra)
|
||||
} else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) {
|
||||
header.Extra = []byte{} // If miner opposes, don't let it use the reserved extra-data
|
||||
}
|
||||
}
|
||||
}
|
||||
statedb, err := api.blockchain.StateAt(parent.Root())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if api.chainConfig.DAOForkSupport && api.chainConfig.DAOForkBlock != nil && api.chainConfig.DAOForkBlock.Cmp(header.Number) == 0 {
|
||||
misc.ApplyDAOHardFork(statedb)
|
||||
}
|
||||
gasPool := new(core.GasPool).AddGas(header.GasLimit)
|
||||
txCount := 0
|
||||
var txs []*types.Transaction
|
||||
var receipts []*types.Receipt
|
||||
var coalescedLogs []*types.Log
|
||||
var blockFull = gasPool.Gas() < params.TxGas
|
||||
for address := range api.txSenders {
|
||||
if blockFull {
|
||||
break
|
||||
}
|
||||
m := api.txMap[address]
|
||||
for nonce := statedb.GetNonce(address); ; nonce++ {
|
||||
if tx, ok := m[nonce]; ok {
|
||||
// Try to apply transactions to the state
|
||||
statedb.Prepare(tx.Hash(), common.Hash{}, txCount)
|
||||
snap := statedb.Snapshot()
|
||||
|
||||
receipt, _, err := core.ApplyTransaction(
|
||||
api.chainConfig,
|
||||
api.blockchain,
|
||||
&api.author,
|
||||
gasPool,
|
||||
statedb,
|
||||
header, tx, &header.GasUsed, *api.blockchain.GetVMConfig(),
|
||||
)
|
||||
if err != nil {
|
||||
statedb.RevertToSnapshot(snap)
|
||||
break
|
||||
}
|
||||
txs = append(txs, tx)
|
||||
receipts = append(receipts, receipt)
|
||||
coalescedLogs = append(coalescedLogs, receipt.Logs...)
|
||||
delete(m, nonce)
|
||||
if len(m) == 0 {
|
||||
// Last tx for the sender
|
||||
delete(api.txMap, address)
|
||||
delete(api.txSenders, address)
|
||||
}
|
||||
txCount++
|
||||
if gasPool.Gas() < params.TxGas {
|
||||
blockFull = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
break // Gap in the nonces
|
||||
}
|
||||
}
|
||||
}
|
||||
block, err := api.engine.FinalizeAndAssemble(api.blockchain, header, statedb, txs, []*types.Header{}, receipts)
|
||||
return api.importBlock(block)
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) importBlock(block *types.Block) error {
|
||||
if _, err := api.blockchain.InsertChain([]*types.Block{block}); err != nil {
|
||||
return err
|
||||
}
|
||||
api.blockNumber = block.NumberU64()
|
||||
fmt.Printf("Imported block %d\n", block.NumberU64())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) ModifyTimestamp(ctx context.Context, interval uint64) (bool, error) {
|
||||
api.blockInterval = interval
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) ImportRawBlock(ctx context.Context, rawBlock hexutil.Bytes) (common.Hash, error) {
|
||||
block := new(types.Block)
|
||||
if err := rlp.DecodeBytes(rawBlock, block); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
fmt.Printf("Importing block %d with parent hash: %x, genesisHash: %x\n", block.NumberU64(), block.ParentHash(), api.genesisHash)
|
||||
if err := api.importBlock(block); err != nil {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
return block.Hash(), nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) RewindToBlock(ctx context.Context, newHead uint64) (bool, error) {
|
||||
if err := api.blockchain.SetHead(newHead); err != nil {
|
||||
return false, err
|
||||
}
|
||||
api.blockNumber = newHead
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var emptyListHash common.Hash = common.HexToHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")
|
||||
|
||||
func (api *RetestethAPI) GetLogHash(ctx context.Context, txHash common.Hash) (common.Hash, error) {
|
||||
receipt, _, _, _ := rawdb.ReadReceipt(api.ethDb, txHash, api.chainConfig)
|
||||
if receipt == nil {
|
||||
return emptyListHash, nil
|
||||
} else {
|
||||
if logListRlp, err := rlp.EncodeToBytes(receipt.Logs); err != nil {
|
||||
return common.Hash{}, err
|
||||
} else {
|
||||
return common.BytesToHash(crypto.Keccak256(logListRlp)), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) BlockNumber(ctx context.Context) (uint64, error) {
|
||||
//fmt.Printf("BlockNumber, response: %d\n", api.blockNumber)
|
||||
return api.blockNumber, nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) GetBlockByNumber(ctx context.Context, blockNr math.HexOrDecimal64, fullTx bool) (map[string]interface{}, error) {
|
||||
block := api.blockchain.GetBlockByNumber(uint64(blockNr))
|
||||
if block != nil {
|
||||
response, err := RPCMarshalBlock(block, true, fullTx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response["author"] = response["miner"]
|
||||
response["totalDifficulty"] = (*hexutil.Big)(api.blockchain.GetTd(block.Hash(), uint64(blockNr)))
|
||||
return response, err
|
||||
}
|
||||
return nil, fmt.Errorf("block %d not found", blockNr)
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) AccountRangeAt(ctx context.Context,
|
||||
blockHashOrNumber *math.HexOrDecimal256, txIndex uint64,
|
||||
addressHash *math.HexOrDecimal256, maxResults uint64,
|
||||
) (AccountRangeResult, error) {
|
||||
var (
|
||||
header *types.Header
|
||||
block *types.Block
|
||||
)
|
||||
if (*big.Int)(blockHashOrNumber).Cmp(big.NewInt(math.MaxInt64)) > 0 {
|
||||
blockHash := common.BigToHash((*big.Int)(blockHashOrNumber))
|
||||
header = api.blockchain.GetHeaderByHash(blockHash)
|
||||
block = api.blockchain.GetBlockByHash(blockHash)
|
||||
//fmt.Printf("Account range: %x, txIndex %d, start: %x, maxResults: %d\n", blockHash, txIndex, common.BigToHash((*big.Int)(addressHash)), maxResults)
|
||||
} else {
|
||||
blockNumber := (*big.Int)(blockHashOrNumber).Uint64()
|
||||
header = api.blockchain.GetHeaderByNumber(blockNumber)
|
||||
block = api.blockchain.GetBlockByNumber(blockNumber)
|
||||
//fmt.Printf("Account range: %d, txIndex %d, start: %x, maxResults: %d\n", blockNumber, txIndex, common.BigToHash((*big.Int)(addressHash)), maxResults)
|
||||
}
|
||||
parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash)
|
||||
var root common.Hash
|
||||
var statedb *state.StateDB
|
||||
var err error
|
||||
if parentHeader == nil || int(txIndex) >= len(block.Transactions()) {
|
||||
root = header.Root
|
||||
statedb, err = api.blockchain.StateAt(root)
|
||||
if err != nil {
|
||||
return AccountRangeResult{}, err
|
||||
}
|
||||
} else {
|
||||
root = parentHeader.Root
|
||||
statedb, err = api.blockchain.StateAt(root)
|
||||
if err != nil {
|
||||
return AccountRangeResult{}, err
|
||||
}
|
||||
// Recompute transactions up to the target index.
|
||||
signer := types.MakeSigner(api.blockchain.Config(), block.Number())
|
||||
for idx, tx := range block.Transactions() {
|
||||
// Assemble the transaction call message and return if the requested offset
|
||||
msg, _ := tx.AsMessage(signer)
|
||||
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
|
||||
// Not yet the searched for transaction, execute on top of the current state
|
||||
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
|
||||
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
||||
return AccountRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||
}
|
||||
// Ensure any modifications are committed to the state
|
||||
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
|
||||
root = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number()))
|
||||
if idx == int(txIndex) {
|
||||
// This is to make sure root can be opened by OpenTrie
|
||||
root, err = statedb.Commit(api.chainConfig.IsEIP158(block.Number()))
|
||||
if err != nil {
|
||||
return AccountRangeResult{}, err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
accountTrie, err := statedb.Database().OpenTrie(root)
|
||||
if err != nil {
|
||||
return AccountRangeResult{}, err
|
||||
}
|
||||
it := trie.NewIterator(accountTrie.NodeIterator(common.BigToHash((*big.Int)(addressHash)).Bytes()))
|
||||
result := AccountRangeResult{AddressMap: make(map[common.Hash]common.Address)}
|
||||
for i := 0; /*i < int(maxResults) && */ it.Next(); i++ {
|
||||
if preimage := accountTrie.GetKey(it.Key); preimage != nil {
|
||||
result.AddressMap[common.BytesToHash(it.Key)] = common.BytesToAddress(preimage)
|
||||
//fmt.Printf("%x: %x\n", it.Key, preimage)
|
||||
} else {
|
||||
//fmt.Printf("could not find preimage for %x\n", it.Key)
|
||||
}
|
||||
}
|
||||
//fmt.Printf("Number of entries returned: %d\n", len(result.AddressMap))
|
||||
// Add the 'next key' so clients can continue downloading.
|
||||
if it.Next() {
|
||||
next := common.BytesToHash(it.Key)
|
||||
result.NextKey = next
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) GetBalance(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (*math.HexOrDecimal256, error) {
|
||||
//fmt.Printf("GetBalance %x, block %d\n", address, blockNr)
|
||||
header := api.blockchain.GetHeaderByNumber(uint64(blockNr))
|
||||
statedb, err := api.blockchain.StateAt(header.Root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*math.HexOrDecimal256)(statedb.GetBalance(address)), nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) GetCode(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (hexutil.Bytes, error) {
|
||||
header := api.blockchain.GetHeaderByNumber(uint64(blockNr))
|
||||
statedb, err := api.blockchain.StateAt(header.Root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return statedb.GetCode(address), nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNr math.HexOrDecimal64) (uint64, error) {
|
||||
header := api.blockchain.GetHeaderByNumber(uint64(blockNr))
|
||||
statedb, err := api.blockchain.StateAt(header.Root)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return statedb.GetNonce(address), nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) StorageRangeAt(ctx context.Context,
|
||||
blockHashOrNumber *math.HexOrDecimal256, txIndex uint64,
|
||||
address common.Address,
|
||||
begin *math.HexOrDecimal256, maxResults uint64,
|
||||
) (StorageRangeResult, error) {
|
||||
var (
|
||||
header *types.Header
|
||||
block *types.Block
|
||||
)
|
||||
if (*big.Int)(blockHashOrNumber).Cmp(big.NewInt(math.MaxInt64)) > 0 {
|
||||
blockHash := common.BigToHash((*big.Int)(blockHashOrNumber))
|
||||
header = api.blockchain.GetHeaderByHash(blockHash)
|
||||
block = api.blockchain.GetBlockByHash(blockHash)
|
||||
//fmt.Printf("Storage range: %x, txIndex %d, addr: %x, start: %x, maxResults: %d\n",
|
||||
// blockHash, txIndex, address, common.BigToHash((*big.Int)(begin)), maxResults)
|
||||
} else {
|
||||
blockNumber := (*big.Int)(blockHashOrNumber).Uint64()
|
||||
header = api.blockchain.GetHeaderByNumber(blockNumber)
|
||||
block = api.blockchain.GetBlockByNumber(blockNumber)
|
||||
//fmt.Printf("Storage range: %d, txIndex %d, addr: %x, start: %x, maxResults: %d\n",
|
||||
// blockNumber, txIndex, address, common.BigToHash((*big.Int)(begin)), maxResults)
|
||||
}
|
||||
parentHeader := api.blockchain.GetHeaderByHash(header.ParentHash)
|
||||
var root common.Hash
|
||||
var statedb *state.StateDB
|
||||
var err error
|
||||
if parentHeader == nil || int(txIndex) >= len(block.Transactions()) {
|
||||
root = header.Root
|
||||
statedb, err = api.blockchain.StateAt(root)
|
||||
if err != nil {
|
||||
return StorageRangeResult{}, err
|
||||
}
|
||||
} else {
|
||||
root = parentHeader.Root
|
||||
statedb, err = api.blockchain.StateAt(root)
|
||||
if err != nil {
|
||||
return StorageRangeResult{}, err
|
||||
}
|
||||
// Recompute transactions up to the target index.
|
||||
signer := types.MakeSigner(api.blockchain.Config(), block.Number())
|
||||
for idx, tx := range block.Transactions() {
|
||||
// Assemble the transaction call message and return if the requested offset
|
||||
msg, _ := tx.AsMessage(signer)
|
||||
context := core.NewEVMContext(msg, block.Header(), api.blockchain, nil)
|
||||
// Not yet the searched for transaction, execute on top of the current state
|
||||
vmenv := vm.NewEVM(context, statedb, api.blockchain.Config(), vm.Config{})
|
||||
if _, _, _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil {
|
||||
return StorageRangeResult{}, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err)
|
||||
}
|
||||
// Ensure any modifications are committed to the state
|
||||
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
|
||||
root = statedb.IntermediateRoot(vmenv.ChainConfig().IsEIP158(block.Number()))
|
||||
if idx == int(txIndex) {
|
||||
// This is to make sure root can be opened by OpenTrie
|
||||
root, err = statedb.Commit(vmenv.ChainConfig().IsEIP158(block.Number()))
|
||||
if err != nil {
|
||||
return StorageRangeResult{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
storageTrie := statedb.StorageTrie(address)
|
||||
it := trie.NewIterator(storageTrie.NodeIterator(common.BigToHash((*big.Int)(begin)).Bytes()))
|
||||
result := StorageRangeResult{Storage: make(map[common.Hash]SRItem)}
|
||||
for i := 0; /*i < int(maxResults) && */ it.Next(); i++ {
|
||||
if preimage := storageTrie.GetKey(it.Key); preimage != nil {
|
||||
key := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(preimage))
|
||||
v, _, err := rlp.SplitString(it.Value)
|
||||
if err != nil {
|
||||
return StorageRangeResult{}, err
|
||||
}
|
||||
value := (*math.HexOrDecimal256)(big.NewInt(0).SetBytes(v))
|
||||
ks, _ := key.MarshalText()
|
||||
vs, _ := value.MarshalText()
|
||||
if len(ks)%2 != 0 {
|
||||
ks = append(append(append([]byte{}, ks[:2]...), byte('0')), ks[2:]...)
|
||||
}
|
||||
if len(vs)%2 != 0 {
|
||||
vs = append(append(append([]byte{}, vs[:2]...), byte('0')), vs[2:]...)
|
||||
}
|
||||
result.Storage[common.BytesToHash(it.Key)] = SRItem{
|
||||
Key: string(ks),
|
||||
Value: string(vs),
|
||||
}
|
||||
//fmt.Printf("Key: %s, Value: %s\n", ks, vs)
|
||||
} else {
|
||||
//fmt.Printf("Did not find preimage for %x\n", it.Key)
|
||||
}
|
||||
}
|
||||
if it.Next() {
|
||||
result.Complete = false
|
||||
} else {
|
||||
result.Complete = true
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (api *RetestethAPI) ClientVersion(ctx context.Context) (string, error) {
|
||||
return "Geth-" + params.VersionWithCommit(gitCommit, gitDate), nil
|
||||
}
|
||||
|
||||
// splitAndTrim splits input separated by a comma
|
||||
// and trims excessive white space from the substrings.
|
||||
func splitAndTrim(input string) []string {
|
||||
result := strings.Split(input, ",")
|
||||
for i, r := range result {
|
||||
result[i] = strings.TrimSpace(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func retesteth(ctx *cli.Context) error {
|
||||
log.Info("Welcome to retesteth!")
|
||||
// register signer API with server
|
||||
var (
|
||||
extapiURL = "n/a"
|
||||
)
|
||||
apiImpl := &RetestethAPI{}
|
||||
var testApi RetestethTestAPI = apiImpl
|
||||
var ethApi RetestethEthAPI = apiImpl
|
||||
var debugApi RetestethDebugAPI = apiImpl
|
||||
var web3Api RetestWeb3API = apiImpl
|
||||
rpcAPI := []rpc.API{
|
||||
{
|
||||
Namespace: "test",
|
||||
Public: true,
|
||||
Service: testApi,
|
||||
Version: "1.0",
|
||||
},
|
||||
{
|
||||
Namespace: "eth",
|
||||
Public: true,
|
||||
Service: ethApi,
|
||||
Version: "1.0",
|
||||
},
|
||||
{
|
||||
Namespace: "debug",
|
||||
Public: true,
|
||||
Service: debugApi,
|
||||
Version: "1.0",
|
||||
},
|
||||
{
|
||||
Namespace: "web3",
|
||||
Public: true,
|
||||
Service: web3Api,
|
||||
Version: "1.0",
|
||||
},
|
||||
}
|
||||
vhosts := splitAndTrim(ctx.GlobalString(utils.RPCVirtualHostsFlag.Name))
|
||||
cors := splitAndTrim(ctx.GlobalString(utils.RPCCORSDomainFlag.Name))
|
||||
|
||||
// start http server
|
||||
httpEndpoint := fmt.Sprintf("%s:%d", ctx.GlobalString(utils.RPCListenAddrFlag.Name), ctx.Int(rpcPortFlag.Name))
|
||||
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"test", "eth", "debug", "web3"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
|
||||
if err != nil {
|
||||
utils.Fatalf("Could not start RPC api: %v", err)
|
||||
}
|
||||
extapiURL = fmt.Sprintf("http://%s", httpEndpoint)
|
||||
log.Info("HTTP endpoint opened", "url", extapiURL)
|
||||
|
||||
defer func() {
|
||||
listener.Close()
|
||||
log.Info("HTTP endpoint closed", "url", httpEndpoint)
|
||||
}()
|
||||
|
||||
abortChan := make(chan os.Signal)
|
||||
signal.Notify(abortChan, os.Interrupt)
|
||||
|
||||
sig := <-abortChan
|
||||
log.Info("Exiting...", "signal", sig)
|
||||
return nil
|
||||
}
|
||||
148
cmd/geth/retesteth_copypaste.go
Normal file
148
cmd/geth/retesteth_copypaste.go
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
|
||||
type RPCTransaction struct {
|
||||
BlockHash common.Hash `json:"blockHash"`
|
||||
BlockNumber *hexutil.Big `json:"blockNumber"`
|
||||
From common.Address `json:"from"`
|
||||
Gas hexutil.Uint64 `json:"gas"`
|
||||
GasPrice *hexutil.Big `json:"gasPrice"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
Input hexutil.Bytes `json:"input"`
|
||||
Nonce hexutil.Uint64 `json:"nonce"`
|
||||
To *common.Address `json:"to"`
|
||||
TransactionIndex hexutil.Uint `json:"transactionIndex"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
V *hexutil.Big `json:"v"`
|
||||
R *hexutil.Big `json:"r"`
|
||||
S *hexutil.Big `json:"s"`
|
||||
}
|
||||
|
||||
// newRPCTransaction returns a transaction that will serialize to the RPC
|
||||
// representation, with the given location metadata set (if available).
|
||||
func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction {
|
||||
var signer types.Signer = types.FrontierSigner{}
|
||||
if tx.Protected() {
|
||||
signer = types.NewEIP155Signer(tx.ChainId())
|
||||
}
|
||||
from, _ := types.Sender(signer, tx)
|
||||
v, r, s := tx.RawSignatureValues()
|
||||
|
||||
result := &RPCTransaction{
|
||||
From: from,
|
||||
Gas: hexutil.Uint64(tx.Gas()),
|
||||
GasPrice: (*hexutil.Big)(tx.GasPrice()),
|
||||
Hash: tx.Hash(),
|
||||
Input: hexutil.Bytes(tx.Data()),
|
||||
Nonce: hexutil.Uint64(tx.Nonce()),
|
||||
To: tx.To(),
|
||||
Value: (*hexutil.Big)(tx.Value()),
|
||||
V: (*hexutil.Big)(v),
|
||||
R: (*hexutil.Big)(r),
|
||||
S: (*hexutil.Big)(s),
|
||||
}
|
||||
if blockHash != (common.Hash{}) {
|
||||
result.BlockHash = blockHash
|
||||
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
|
||||
result.TransactionIndex = hexutil.Uint(index)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation.
|
||||
func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction {
|
||||
txs := b.Transactions()
|
||||
if index >= uint64(len(txs)) {
|
||||
return nil
|
||||
}
|
||||
return newRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index)
|
||||
}
|
||||
|
||||
// newRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation.
|
||||
func newRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction {
|
||||
for idx, tx := range b.Transactions() {
|
||||
if tx.Hash() == hash {
|
||||
return newRPCTransactionFromBlockIndex(b, uint64(idx))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are
|
||||
// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain
|
||||
// transaction hashes.
|
||||
func RPCMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) {
|
||||
head := b.Header() // copies the header once
|
||||
fields := map[string]interface{}{
|
||||
"number": (*hexutil.Big)(head.Number),
|
||||
"hash": b.Hash(),
|
||||
"parentHash": head.ParentHash,
|
||||
"nonce": head.Nonce,
|
||||
"mixHash": head.MixDigest,
|
||||
"sha3Uncles": head.UncleHash,
|
||||
"logsBloom": head.Bloom,
|
||||
"stateRoot": head.Root,
|
||||
"miner": head.Coinbase,
|
||||
"difficulty": (*hexutil.Big)(head.Difficulty),
|
||||
"extraData": hexutil.Bytes(head.Extra),
|
||||
"size": hexutil.Uint64(b.Size()),
|
||||
"gasLimit": hexutil.Uint64(head.GasLimit),
|
||||
"gasUsed": hexutil.Uint64(head.GasUsed),
|
||||
"timestamp": hexutil.Uint64(head.Time),
|
||||
"transactionsRoot": head.TxHash,
|
||||
"receiptsRoot": head.ReceiptHash,
|
||||
}
|
||||
|
||||
if inclTx {
|
||||
formatTx := func(tx *types.Transaction) (interface{}, error) {
|
||||
return tx.Hash(), nil
|
||||
}
|
||||
if fullTx {
|
||||
formatTx = func(tx *types.Transaction) (interface{}, error) {
|
||||
return newRPCTransactionFromBlockHash(b, tx.Hash()), nil
|
||||
}
|
||||
}
|
||||
txs := b.Transactions()
|
||||
transactions := make([]interface{}, len(txs))
|
||||
var err error
|
||||
for i, tx := range txs {
|
||||
if transactions[i], err = formatTx(tx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
fields["transactions"] = transactions
|
||||
}
|
||||
|
||||
uncles := b.Uncles()
|
||||
uncleHashes := make([]common.Hash, len(uncles))
|
||||
for i, uncle := range uncles {
|
||||
uncleHashes[i] = uncle.Hash()
|
||||
}
|
||||
fields["uncles"] = uncleHashes
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
|
@ -72,6 +72,7 @@ var AppHelpFlagGroups = []flagGroup{
|
|||
utils.AncientFlag,
|
||||
utils.KeyStoreDirFlag,
|
||||
utils.NoUSBFlag,
|
||||
utils.SmartCardDaemonPathFlag,
|
||||
utils.NetworkIdFlag,
|
||||
utils.TestnetFlag,
|
||||
utils.RinkebyFlag,
|
||||
|
|
|
|||
|
|
@ -1,297 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
"github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
salt = make([]byte, 32)
|
||||
accessCommand = cli.Command{
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "access",
|
||||
Usage: "encrypts a reference and embeds it into a root manifest",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "encrypts a reference and embeds it into a root manifest",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "new",
|
||||
Usage: "encrypts a reference and embeds it into a root manifest",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Action: accessNewPass,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Flags: []cli.Flag{
|
||||
utils.PasswordFileFlag,
|
||||
SwarmDryRunFlag,
|
||||
},
|
||||
Name: "pass",
|
||||
Usage: "encrypts a reference with a password and embeds it into a root manifest",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||
},
|
||||
{
|
||||
Action: accessNewPK,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Flags: []cli.Flag{
|
||||
utils.PasswordFileFlag,
|
||||
SwarmDryRunFlag,
|
||||
SwarmAccessGrantKeyFlag,
|
||||
},
|
||||
Name: "pk",
|
||||
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||
},
|
||||
{
|
||||
Action: accessNewACT,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Flags: []cli.Flag{
|
||||
SwarmAccessGrantKeysFlag,
|
||||
SwarmDryRunFlag,
|
||||
utils.PasswordFileFlag,
|
||||
},
|
||||
Name: "act",
|
||||
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||
panic("reading from crypto/rand failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func accessNewPass(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) != 1 {
|
||||
utils.Fatalf("Expected 1 argument - the ref")
|
||||
}
|
||||
|
||||
var (
|
||||
ae *api.AccessEntry
|
||||
accessKey []byte
|
||||
err error
|
||||
ref = args[0]
|
||||
password = getPassPhrase("", 0, makePasswordList(ctx))
|
||||
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
|
||||
)
|
||||
accessKey, ae, err = api.DoPassword(ctx, password, salt)
|
||||
if err != nil {
|
||||
utils.Fatalf("error getting session key: %v", err)
|
||||
}
|
||||
m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error generating the manifest: %v", err)
|
||||
}
|
||||
if dryRun {
|
||||
err = printManifests(m, nil)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error printing the manifests: %v", err)
|
||||
}
|
||||
} else {
|
||||
err = uploadManifests(ctx, m, nil)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error uploading the manifests: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func accessNewPK(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) != 1 {
|
||||
utils.Fatalf("Expected 1 argument - the ref")
|
||||
}
|
||||
|
||||
var (
|
||||
ae *api.AccessEntry
|
||||
sessionKey []byte
|
||||
err error
|
||||
ref = args[0]
|
||||
privateKey = getPrivKey(ctx)
|
||||
granteePublicKey = ctx.String(SwarmAccessGrantKeyFlag.Name)
|
||||
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
|
||||
)
|
||||
sessionKey, ae, err = api.DoPK(ctx, privateKey, granteePublicKey, salt)
|
||||
if err != nil {
|
||||
utils.Fatalf("error getting session key: %v", err)
|
||||
}
|
||||
m, err := api.GenerateAccessControlManifest(ctx, ref, sessionKey, ae)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error generating the manifest: %v", err)
|
||||
}
|
||||
if dryRun {
|
||||
err = printManifests(m, nil)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error printing the manifests: %v", err)
|
||||
}
|
||||
} else {
|
||||
err = uploadManifests(ctx, m, nil)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error uploading the manifests: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func accessNewACT(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) != 1 {
|
||||
utils.Fatalf("Expected 1 argument - the ref")
|
||||
}
|
||||
|
||||
var (
|
||||
ae *api.AccessEntry
|
||||
actManifest *api.Manifest
|
||||
accessKey []byte
|
||||
err error
|
||||
ref = args[0]
|
||||
pkGrantees []string
|
||||
passGrantees []string
|
||||
pkGranteesFilename = ctx.String(SwarmAccessGrantKeysFlag.Name)
|
||||
passGranteesFilename = ctx.String(utils.PasswordFileFlag.Name)
|
||||
privateKey = getPrivKey(ctx)
|
||||
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
|
||||
)
|
||||
if pkGranteesFilename == "" && passGranteesFilename == "" {
|
||||
utils.Fatalf("you have to provide either a grantee public-keys file or an encryption passwords file (or both)")
|
||||
}
|
||||
|
||||
if pkGranteesFilename != "" {
|
||||
bytes, err := ioutil.ReadFile(pkGranteesFilename)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error reading the grantee public key list")
|
||||
}
|
||||
pkGrantees = strings.Split(strings.Trim(string(bytes), "\n"), "\n")
|
||||
}
|
||||
|
||||
if passGranteesFilename != "" {
|
||||
bytes, err := ioutil.ReadFile(passGranteesFilename)
|
||||
if err != nil {
|
||||
utils.Fatalf("could not read password filename: %v", err)
|
||||
}
|
||||
passGrantees = strings.Split(strings.Trim(string(bytes), "\n"), "\n")
|
||||
}
|
||||
accessKey, ae, actManifest, err = api.DoACT(ctx, privateKey, salt, pkGrantees, passGrantees)
|
||||
if err != nil {
|
||||
utils.Fatalf("error generating ACT manifest: %v", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.Fatalf("error getting session key: %v", err)
|
||||
}
|
||||
m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
|
||||
if err != nil {
|
||||
utils.Fatalf("error generating root access manifest: %v", err)
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
err = printManifests(m, actManifest)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error printing the manifests: %v", err)
|
||||
}
|
||||
} else {
|
||||
err = uploadManifests(ctx, m, actManifest)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error uploading the manifests: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printManifests(rootAccessManifest, actManifest *api.Manifest) error {
|
||||
js, err := json.Marshal(rootAccessManifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(js))
|
||||
|
||||
if actManifest != nil {
|
||||
js, err := json.Marshal(actManifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(js))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func uploadManifests(ctx *cli.Context, rootAccessManifest, actManifest *api.Manifest) error {
|
||||
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
client := client.NewClient(bzzapi)
|
||||
|
||||
var (
|
||||
key string
|
||||
err error
|
||||
)
|
||||
if actManifest != nil {
|
||||
key, err = client.UploadManifest(actManifest, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rootAccessManifest.Entries[0].Access.Act = key
|
||||
}
|
||||
key, err = client.UploadManifest(rootAccessManifest, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// makePasswordList reads password lines from the file specified by the global --password flag
|
||||
// and also by the same subcommand --password flag.
|
||||
// This function ia a fork of utils.MakePasswordList to lookup cli context for subcommand.
|
||||
// Function ctx.SetGlobal is not setting the global flag value that can be accessed
|
||||
// by ctx.GlobalString using the current version of cli package.
|
||||
func makePasswordList(ctx *cli.Context) []string {
|
||||
path := ctx.GlobalString(utils.PasswordFileFlag.Name)
|
||||
if path == "" {
|
||||
path = ctx.String(utils.PasswordFileFlag.Name)
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
text, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read password file: %v", err)
|
||||
}
|
||||
lines := strings.Split(string(text), "\n")
|
||||
// Sanitise DOS line endings.
|
||||
for i := range lines {
|
||||
lines[i] = strings.TrimRight(lines[i], "\r")
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
|
@ -1,617 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
gorand "math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
swarmapi "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
const (
|
||||
hashRegexp = `[a-f\d]{128}`
|
||||
data = "notsorandomdata"
|
||||
)
|
||||
|
||||
var DefaultCurve = crypto.S256()
|
||||
|
||||
func TestACT(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
cluster := newTestCluster(t, clusterSize)
|
||||
defer cluster.Shutdown()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
f func(t *testing.T, cluster *testCluster)
|
||||
}{
|
||||
{"Password", testPassword},
|
||||
{"PK", testPK},
|
||||
{"ACTWithoutBogus", testACTWithoutBogus},
|
||||
{"ACTWithBogus", testACTWithBogus},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.f(t, cluster)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// testPassword tests for the correct creation of an ACT manifest protected by a password.
|
||||
// The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry
|
||||
// The parties participating - node (publisher), uploads to second node then disappears. Content which was uploaded
|
||||
// is then fetched through 2nd node. since the tested code is not key-aware - we can just
|
||||
// fetch from the 2nd node using HTTP BasicAuth
|
||||
func testPassword(t *testing.T, cluster *testCluster) {
|
||||
dataFilename := testutil.TempFileWithContent(t, data)
|
||||
defer os.RemoveAll(dataFilename)
|
||||
|
||||
// upload the file with 'swarm up' and expect a hash
|
||||
up := runSwarm(t,
|
||||
"--bzzapi",
|
||||
cluster.Nodes[0].URL,
|
||||
"up",
|
||||
"--encrypt",
|
||||
dataFilename)
|
||||
_, matches := up.ExpectRegexp(hashRegexp)
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) < 1 {
|
||||
t.Fatal("no matches found")
|
||||
}
|
||||
|
||||
ref := matches[0]
|
||||
tmp, err := ioutil.TempDir("", "swarm-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
password := "smth"
|
||||
passwordFilename := testutil.TempFileWithContent(t, "smth")
|
||||
defer os.RemoveAll(passwordFilename)
|
||||
|
||||
up = runSwarm(t,
|
||||
"access",
|
||||
"new",
|
||||
"pass",
|
||||
"--dry-run",
|
||||
"--password",
|
||||
passwordFilename,
|
||||
ref,
|
||||
)
|
||||
|
||||
_, matches = up.ExpectRegexp(".+")
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) == 0 {
|
||||
t.Fatalf("stdout not matched")
|
||||
}
|
||||
|
||||
var m api.Manifest
|
||||
|
||||
err = json.Unmarshal([]byte(matches[0]), &m)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal manifest: %v", err)
|
||||
}
|
||||
|
||||
if len(m.Entries) != 1 {
|
||||
t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
|
||||
}
|
||||
|
||||
e := m.Entries[0]
|
||||
|
||||
ct := "application/bzz-manifest+json"
|
||||
if e.ContentType != ct {
|
||||
t.Errorf("expected %q content type, got %q", ct, e.ContentType)
|
||||
}
|
||||
|
||||
if e.Access == nil {
|
||||
t.Fatal("manifest access is nil")
|
||||
}
|
||||
|
||||
a := e.Access
|
||||
|
||||
if a.Type != "pass" {
|
||||
t.Errorf(`got access type %q, expected "pass"`, a.Type)
|
||||
}
|
||||
if len(a.Salt) < 32 {
|
||||
t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
|
||||
}
|
||||
if a.KdfParams == nil {
|
||||
t.Fatal("manifest access kdf params is nil")
|
||||
}
|
||||
if a.Publisher != "" {
|
||||
t.Fatal("should be empty")
|
||||
}
|
||||
|
||||
client := swarmapi.NewClient(cluster.Nodes[0].URL)
|
||||
|
||||
hash, err := client.UploadManifest(&m, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash
|
||||
|
||||
httpClient := &http.Client{}
|
||||
response, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatal("should be a 401")
|
||||
}
|
||||
authHeader := response.Header.Get("WWW-Authenticate")
|
||||
if authHeader == "" {
|
||||
t.Fatal("should be something here")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.SetBasicAuth("", password)
|
||||
|
||||
response, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
t.Errorf("expected status %v, got %v", http.StatusOK, response.StatusCode)
|
||||
}
|
||||
d, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(d) != data {
|
||||
t.Errorf("expected decrypted data %q, got %q", data, string(d))
|
||||
}
|
||||
|
||||
wrongPasswordFilename := testutil.TempFileWithContent(t, "just wr0ng")
|
||||
defer os.RemoveAll(wrongPasswordFilename)
|
||||
|
||||
//download file with 'swarm down' with wrong password
|
||||
up = runSwarm(t,
|
||||
"--bzzapi",
|
||||
cluster.Nodes[0].URL,
|
||||
"down",
|
||||
"bzz:/"+hash,
|
||||
tmp,
|
||||
"--password",
|
||||
wrongPasswordFilename)
|
||||
|
||||
_, matches = up.ExpectRegexp("unauthorized")
|
||||
if len(matches) != 1 && matches[0] != "unauthorized" {
|
||||
t.Fatal(`"unauthorized" not found in output"`)
|
||||
}
|
||||
up.ExpectExit()
|
||||
}
|
||||
|
||||
// testPK tests for the correct creation of an ACT manifest between two parties (publisher and grantee).
|
||||
// The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry
|
||||
// The parties participating - node (publisher), uploads to second node (which is also the grantee) then disappears.
|
||||
// Content which was uploaded is then fetched through the grantee's http proxy. Since the tested code is private-key aware,
|
||||
// the test will fail if the proxy's given private key is not granted on the ACT.
|
||||
func testPK(t *testing.T, cluster *testCluster) {
|
||||
dataFilename := testutil.TempFileWithContent(t, data)
|
||||
defer os.RemoveAll(dataFilename)
|
||||
|
||||
// upload the file with 'swarm up' and expect a hash
|
||||
up := runSwarm(t,
|
||||
"--bzzapi",
|
||||
cluster.Nodes[0].URL,
|
||||
"up",
|
||||
"--encrypt",
|
||||
dataFilename)
|
||||
_, matches := up.ExpectRegexp(hashRegexp)
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) < 1 {
|
||||
t.Fatal("no matches found")
|
||||
}
|
||||
|
||||
ref := matches[0]
|
||||
pk := cluster.Nodes[0].PrivateKey
|
||||
granteePubKey := crypto.CompressPubkey(&pk.PublicKey)
|
||||
|
||||
publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
passwordFilename := testutil.TempFileWithContent(t, testPassphrase)
|
||||
defer os.RemoveAll(passwordFilename)
|
||||
|
||||
_, publisherAccount := getTestAccount(t, publisherDir)
|
||||
up = runSwarm(t,
|
||||
"--bzzaccount",
|
||||
publisherAccount.Address.String(),
|
||||
"--password",
|
||||
passwordFilename,
|
||||
"--datadir",
|
||||
publisherDir,
|
||||
"--bzzapi",
|
||||
cluster.Nodes[0].URL,
|
||||
"access",
|
||||
"new",
|
||||
"pk",
|
||||
"--dry-run",
|
||||
"--grant-key",
|
||||
hex.EncodeToString(granteePubKey),
|
||||
ref,
|
||||
)
|
||||
|
||||
_, matches = up.ExpectRegexp(".+")
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) == 0 {
|
||||
t.Fatalf("stdout not matched")
|
||||
}
|
||||
|
||||
//get the public key from the publisher directory
|
||||
publicKeyFromDataDir := runSwarm(t,
|
||||
"--bzzaccount",
|
||||
publisherAccount.Address.String(),
|
||||
"--password",
|
||||
passwordFilename,
|
||||
"--datadir",
|
||||
publisherDir,
|
||||
"print-keys",
|
||||
"--compressed",
|
||||
)
|
||||
_, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+")
|
||||
publicKeyFromDataDir.ExpectExit()
|
||||
pkComp := strings.Split(publicKeyString[0], "=")[1]
|
||||
var m api.Manifest
|
||||
|
||||
err = json.Unmarshal([]byte(matches[0]), &m)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal manifest: %v", err)
|
||||
}
|
||||
|
||||
if len(m.Entries) != 1 {
|
||||
t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
|
||||
}
|
||||
|
||||
e := m.Entries[0]
|
||||
|
||||
ct := "application/bzz-manifest+json"
|
||||
if e.ContentType != ct {
|
||||
t.Errorf("expected %q content type, got %q", ct, e.ContentType)
|
||||
}
|
||||
|
||||
if e.Access == nil {
|
||||
t.Fatal("manifest access is nil")
|
||||
}
|
||||
|
||||
a := e.Access
|
||||
|
||||
if a.Type != "pk" {
|
||||
t.Errorf(`got access type %q, expected "pk"`, a.Type)
|
||||
}
|
||||
if len(a.Salt) < 32 {
|
||||
t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
|
||||
}
|
||||
if a.KdfParams != nil {
|
||||
t.Fatal("manifest access kdf params should be nil")
|
||||
}
|
||||
if a.Publisher != pkComp {
|
||||
t.Fatal("publisher key did not match")
|
||||
}
|
||||
client := swarmapi.NewClient(cluster.Nodes[0].URL)
|
||||
|
||||
hash, err := client.UploadManifest(&m, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
httpClient := &http.Client{}
|
||||
|
||||
url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash
|
||||
response, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
t.Fatal("should be a 200")
|
||||
}
|
||||
d, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(d) != data {
|
||||
t.Errorf("expected decrypted data %q, got %q", data, string(d))
|
||||
}
|
||||
}
|
||||
|
||||
// testACTWithoutBogus tests the creation of the ACT manifest end-to-end, without any bogus entries (i.e. default scenario = 3 nodes 1 unauthorized)
|
||||
func testACTWithoutBogus(t *testing.T, cluster *testCluster) {
|
||||
testACT(t, cluster, 0)
|
||||
}
|
||||
|
||||
// testACTWithBogus tests the creation of the ACT manifest end-to-end, with 100 bogus entries (i.e. 100 EC keys + default scenario = 3 nodes 1 unauthorized = 103 keys in the ACT manifest)
|
||||
func testACTWithBogus(t *testing.T, cluster *testCluster) {
|
||||
testACT(t, cluster, 100)
|
||||
}
|
||||
|
||||
// testACT tests the e2e creation, uploading and downloading of an ACT access control with both EC keys AND password protection
|
||||
// the test fires up a 3 node cluster, then randomly picks 2 nodes which will be acting as grantees to the data
|
||||
// set and also protects the ACT with a password. the third node should fail decoding the reference as it will not be granted access.
|
||||
// the third node then then tries to download using a correct password (and succeeds) then uses a wrong password and fails.
|
||||
// the publisher uploads through one of the nodes then disappears.
|
||||
func testACT(t *testing.T, cluster *testCluster, bogusEntries int) {
|
||||
var uploadThroughNode = cluster.Nodes[0]
|
||||
client := swarmapi.NewClient(uploadThroughNode.URL)
|
||||
|
||||
r1 := gorand.New(gorand.NewSource(time.Now().UnixNano()))
|
||||
nodeToSkip := r1.Intn(clusterSize) // a number between 0 and 2 (node indices in `cluster`)
|
||||
dataFilename := testutil.TempFileWithContent(t, data)
|
||||
defer os.RemoveAll(dataFilename)
|
||||
|
||||
// upload the file with 'swarm up' and expect a hash
|
||||
up := runSwarm(t,
|
||||
"--bzzapi",
|
||||
cluster.Nodes[0].URL,
|
||||
"up",
|
||||
"--encrypt",
|
||||
dataFilename)
|
||||
_, matches := up.ExpectRegexp(hashRegexp)
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) < 1 {
|
||||
t.Fatal("no matches found")
|
||||
}
|
||||
|
||||
ref := matches[0]
|
||||
var grantees []string
|
||||
for i, v := range cluster.Nodes {
|
||||
if i == nodeToSkip {
|
||||
continue
|
||||
}
|
||||
pk := v.PrivateKey
|
||||
granteePubKey := crypto.CompressPubkey(&pk.PublicKey)
|
||||
grantees = append(grantees, hex.EncodeToString(granteePubKey))
|
||||
}
|
||||
|
||||
if bogusEntries > 0 {
|
||||
var bogusGrantees []string
|
||||
|
||||
for i := 0; i < bogusEntries; i++ {
|
||||
prv, err := ecies.GenerateKey(rand.Reader, DefaultCurve, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bogusGrantees = append(bogusGrantees, hex.EncodeToString(crypto.CompressPubkey(&prv.ExportECDSA().PublicKey)))
|
||||
}
|
||||
r2 := gorand.New(gorand.NewSource(time.Now().UnixNano()))
|
||||
for i := 0; i < len(grantees); i++ {
|
||||
insertAtIdx := r2.Intn(len(bogusGrantees))
|
||||
bogusGrantees = append(bogusGrantees[:insertAtIdx], append([]string{grantees[i]}, bogusGrantees[insertAtIdx:]...)...)
|
||||
}
|
||||
grantees = bogusGrantees
|
||||
}
|
||||
granteesPubkeyListFile := testutil.TempFileWithContent(t, strings.Join(grantees, "\n"))
|
||||
defer os.RemoveAll(granteesPubkeyListFile)
|
||||
|
||||
publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(publisherDir)
|
||||
|
||||
passwordFilename := testutil.TempFileWithContent(t, testPassphrase)
|
||||
defer os.RemoveAll(passwordFilename)
|
||||
actPasswordFilename := testutil.TempFileWithContent(t, "smth")
|
||||
defer os.RemoveAll(actPasswordFilename)
|
||||
_, publisherAccount := getTestAccount(t, publisherDir)
|
||||
up = runSwarm(t,
|
||||
"--bzzaccount",
|
||||
publisherAccount.Address.String(),
|
||||
"--password",
|
||||
passwordFilename,
|
||||
"--datadir",
|
||||
publisherDir,
|
||||
"--bzzapi",
|
||||
cluster.Nodes[0].URL,
|
||||
"access",
|
||||
"new",
|
||||
"act",
|
||||
"--grant-keys",
|
||||
granteesPubkeyListFile,
|
||||
"--password",
|
||||
actPasswordFilename,
|
||||
ref,
|
||||
)
|
||||
|
||||
_, matches = up.ExpectRegexp(`[a-f\d]{64}`)
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) == 0 {
|
||||
t.Fatalf("stdout not matched")
|
||||
}
|
||||
|
||||
//get the public key from the publisher directory
|
||||
publicKeyFromDataDir := runSwarm(t,
|
||||
"--bzzaccount",
|
||||
publisherAccount.Address.String(),
|
||||
"--password",
|
||||
passwordFilename,
|
||||
"--datadir",
|
||||
publisherDir,
|
||||
"print-keys",
|
||||
"--compressed",
|
||||
)
|
||||
_, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+")
|
||||
publicKeyFromDataDir.ExpectExit()
|
||||
pkComp := strings.Split(publicKeyString[0], "=")[1]
|
||||
|
||||
hash := matches[0]
|
||||
m, _, err := client.DownloadManifest(hash)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal manifest: %v", err)
|
||||
}
|
||||
|
||||
if len(m.Entries) != 1 {
|
||||
t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
|
||||
}
|
||||
|
||||
e := m.Entries[0]
|
||||
|
||||
ct := "application/bzz-manifest+json"
|
||||
if e.ContentType != ct {
|
||||
t.Errorf("expected %q content type, got %q", ct, e.ContentType)
|
||||
}
|
||||
|
||||
if e.Access == nil {
|
||||
t.Fatal("manifest access is nil")
|
||||
}
|
||||
|
||||
a := e.Access
|
||||
|
||||
if a.Type != "act" {
|
||||
t.Fatalf(`got access type %q, expected "act"`, a.Type)
|
||||
}
|
||||
if len(a.Salt) < 32 {
|
||||
t.Fatalf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
|
||||
}
|
||||
|
||||
if a.Publisher != pkComp {
|
||||
t.Fatal("publisher key did not match")
|
||||
}
|
||||
httpClient := &http.Client{}
|
||||
|
||||
// all nodes except the skipped node should be able to decrypt the content
|
||||
for i, node := range cluster.Nodes {
|
||||
log.Debug("trying to fetch from node", "node index", i)
|
||||
|
||||
url := node.URL + "/" + "bzz:/" + hash
|
||||
response, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Debug("got response from node", "response code", response.StatusCode)
|
||||
|
||||
if i == nodeToSkip {
|
||||
log.Debug("reached node to skip", "status code", response.StatusCode)
|
||||
|
||||
if response.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatalf("should be a 401")
|
||||
}
|
||||
|
||||
// try downloading using a password instead, using the unauthorized node
|
||||
passwordUrl := strings.Replace(url, "http://", "http://:smth@", -1)
|
||||
response, err = httpClient.Get(passwordUrl)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
t.Fatal("should be a 200")
|
||||
}
|
||||
|
||||
// now try with the wrong password, expect 401
|
||||
passwordUrl = strings.Replace(url, "http://", "http://:smthWrong@", -1)
|
||||
response, err = httpClient.Get(passwordUrl)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatal("should be a 401")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
t.Fatal("should be a 200")
|
||||
}
|
||||
d, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(d) != data {
|
||||
t.Errorf("expected decrypted data %q, got %q", data, string(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestKeypairSanity is a sanity test for the crypto scheme for ACT. it asserts the correct shared secret according to
|
||||
// the specs at https://github.com/ethersphere/swarm-docs/blob/eb857afda906c6e7bb90d37f3f334ccce5eef230/act.md
|
||||
func TestKeypairSanity(t *testing.T) {
|
||||
salt := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||
t.Fatalf("reading from crypto/rand failed: %v", err.Error())
|
||||
}
|
||||
sharedSecret := "a85586744a1ddd56a7ed9f33fa24f40dd745b3a941be296a0d60e329dbdb896d"
|
||||
|
||||
for i, v := range []struct {
|
||||
publisherPriv string
|
||||
granteePub string
|
||||
}{
|
||||
{
|
||||
publisherPriv: "ec5541555f3bc6376788425e9d1a62f55a82901683fd7062c5eddcc373a73459",
|
||||
granteePub: "0226f213613e843a413ad35b40f193910d26eb35f00154afcde9ded57479a6224a",
|
||||
},
|
||||
{
|
||||
publisherPriv: "70c7a73011aa56584a0009ab874794ee7e5652fd0c6911cd02f8b6267dd82d2d",
|
||||
granteePub: "02e6f8d5e28faaa899744972bb847b6eb805a160494690c9ee7197ae9f619181db",
|
||||
},
|
||||
} {
|
||||
b, _ := hex.DecodeString(v.granteePub)
|
||||
granteePub, _ := crypto.DecompressPubkey(b)
|
||||
publisherPrivate, _ := crypto.HexToECDSA(v.publisherPriv)
|
||||
|
||||
ssKey, err := api.NewSessionKeyPK(publisherPrivate, granteePub, salt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hasher := sha3.NewLegacyKeccak256()
|
||||
hasher.Write(salt)
|
||||
shared, err := hex.DecodeString(sharedSecret)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hasher.Write(shared)
|
||||
sum := hasher.Sum(nil)
|
||||
|
||||
if !bytes.Equal(ssKey, sum) {
|
||||
t.Fatalf("%d: got a session key mismatch", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
var SwarmBootnodes = []string{
|
||||
// EF Swarm Bootnode - AWS - eu-central-1
|
||||
"enode://4c113504601930bf2000c29bcd98d1716b6167749f58bad703bae338332fe93cc9d9204f08afb44100dc7bea479205f5d162df579f9a8f76f8b402d339709023@3.122.203.99:30301",
|
||||
// EF Swarm Bootnode - AWS - us-west-2
|
||||
"enode://89f2ede3371bff1ad9f2088f2012984e280287a4e2b68007c2a6ad994909c51886b4a8e9e2ecc97f9910aca538398e0a5804b0ee80a187fde1ba4f32626322ba@52.35.212.179:30301",
|
||||
}
|
||||
|
|
@ -1,451 +0,0 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/naoina/toml"
|
||||
|
||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||
)
|
||||
|
||||
var (
|
||||
//flag definition for the dumpconfig command
|
||||
DumpConfigCommand = cli.Command{
|
||||
Action: utils.MigrateFlags(dumpConfig),
|
||||
Name: "dumpconfig",
|
||||
Usage: "Show configuration values",
|
||||
ArgsUsage: "",
|
||||
Flags: app.Flags,
|
||||
Category: "MISCELLANEOUS COMMANDS",
|
||||
Description: `The dumpconfig command shows configuration values.`,
|
||||
}
|
||||
|
||||
//flag definition for the config file command
|
||||
SwarmTomlConfigPathFlag = cli.StringFlag{
|
||||
Name: "config",
|
||||
Usage: "TOML configuration file",
|
||||
}
|
||||
)
|
||||
|
||||
//constants for environment variables
|
||||
const (
|
||||
SwarmEnvChequebookAddr = "SWARM_CHEQUEBOOK_ADDR"
|
||||
SwarmEnvAccount = "SWARM_ACCOUNT"
|
||||
SwarmEnvListenAddr = "SWARM_LISTEN_ADDR"
|
||||
SwarmEnvPort = "SWARM_PORT"
|
||||
SwarmEnvNetworkID = "SWARM_NETWORK_ID"
|
||||
SwarmEnvSwapEnable = "SWARM_SWAP_ENABLE"
|
||||
SwarmEnvSwapAPI = "SWARM_SWAP_API"
|
||||
SwarmEnvSyncDisable = "SWARM_SYNC_DISABLE"
|
||||
SwarmEnvSyncUpdateDelay = "SWARM_ENV_SYNC_UPDATE_DELAY"
|
||||
SwarmEnvMaxStreamPeerServers = "SWARM_ENV_MAX_STREAM_PEER_SERVERS"
|
||||
SwarmEnvLightNodeEnable = "SWARM_LIGHT_NODE_ENABLE"
|
||||
SwarmEnvDeliverySkipCheck = "SWARM_DELIVERY_SKIP_CHECK"
|
||||
SwarmEnvENSAPI = "SWARM_ENS_API"
|
||||
SwarmEnvENSAddr = "SWARM_ENS_ADDR"
|
||||
SwarmEnvCORS = "SWARM_CORS"
|
||||
SwarmEnvBootnodes = "SWARM_BOOTNODES"
|
||||
SwarmEnvPSSEnable = "SWARM_PSS_ENABLE"
|
||||
SwarmEnvStorePath = "SWARM_STORE_PATH"
|
||||
SwarmEnvStoreCapacity = "SWARM_STORE_CAPACITY"
|
||||
SwarmEnvStoreCacheCapacity = "SWARM_STORE_CACHE_CAPACITY"
|
||||
SwarmEnvBootnodeMode = "SWARM_BOOTNODE_MODE"
|
||||
SwarmAccessPassword = "SWARM_ACCESS_PASSWORD"
|
||||
SwarmAutoDefaultPath = "SWARM_AUTO_DEFAULTPATH"
|
||||
SwarmGlobalstoreAPI = "SWARM_GLOBALSTORE_API"
|
||||
GethEnvDataDir = "GETH_DATADIR"
|
||||
)
|
||||
|
||||
// These settings ensure that TOML keys use the same names as Go struct fields.
|
||||
var tomlSettings = toml.Config{
|
||||
NormFieldName: func(rt reflect.Type, key string) string {
|
||||
return key
|
||||
},
|
||||
FieldToKey: func(rt reflect.Type, field string) string {
|
||||
return field
|
||||
},
|
||||
MissingField: func(rt reflect.Type, field string) error {
|
||||
link := ""
|
||||
if unicode.IsUpper(rune(rt.Name()[0])) && rt.PkgPath() != "main" {
|
||||
link = fmt.Sprintf(", check github.com/ethereum/go-ethereum/swarm/api/config.go for available fields")
|
||||
}
|
||||
return fmt.Errorf("field '%s' is not defined in %s%s", field, rt.String(), link)
|
||||
},
|
||||
}
|
||||
|
||||
//before booting the swarm node, build the configuration
|
||||
func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
|
||||
//start by creating a default config
|
||||
config = bzzapi.NewConfig()
|
||||
//first load settings from config file (if provided)
|
||||
config, err = configFileOverride(config, ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//override settings provided by environment variables
|
||||
config = envVarsOverride(config)
|
||||
//override settings provided by command line
|
||||
config = cmdLineOverride(config, ctx)
|
||||
//validate configuration parameters
|
||||
err = validateConfig(config)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
//finally, after the configuration build phase is finished, initialize
|
||||
func initSwarmNode(config *bzzapi.Config, stack *node.Node, ctx *cli.Context, nodeconfig *node.Config) error {
|
||||
//at this point, all vars should be set in the Config
|
||||
//get the account for the provided swarm account
|
||||
prvkey := getAccount(config.BzzAccount, ctx, stack)
|
||||
//set the resolved config path (geth --datadir)
|
||||
config.Path = expandPath(stack.InstanceDir())
|
||||
//finally, initialize the configuration
|
||||
err := config.Init(prvkey, nodeconfig.NodeKey())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//configuration phase completed here
|
||||
log.Debug("Starting Swarm with the following parameters:")
|
||||
//after having created the config, print it to screen
|
||||
log.Debug(printConfig(config))
|
||||
return nil
|
||||
}
|
||||
|
||||
//configFileOverride overrides the current config with the config file, if a config file has been provided
|
||||
func configFileOverride(config *bzzapi.Config, ctx *cli.Context) (*bzzapi.Config, error) {
|
||||
var err error
|
||||
|
||||
//only do something if the -config flag has been set
|
||||
if ctx.GlobalIsSet(SwarmTomlConfigPathFlag.Name) {
|
||||
var filepath string
|
||||
if filepath = ctx.GlobalString(SwarmTomlConfigPathFlag.Name); filepath == "" {
|
||||
utils.Fatalf("Config file flag provided with invalid file path")
|
||||
}
|
||||
var f *os.File
|
||||
f, err = os.Open(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
//decode the TOML file into a Config struct
|
||||
//note that we are decoding into the existing defaultConfig;
|
||||
//if an entry is not present in the file, the default entry is kept
|
||||
err = tomlSettings.NewDecoder(f).Decode(&config)
|
||||
// Add file name to errors that have a line number.
|
||||
if _, ok := err.(*toml.LineError); ok {
|
||||
err = errors.New(filepath + ", " + err.Error())
|
||||
}
|
||||
}
|
||||
return config, err
|
||||
}
|
||||
|
||||
// cmdLineOverride overrides the current config with whatever is provided through the command line
|
||||
// most values are not allowed a zero value (empty string), if not otherwise noted
|
||||
func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Config {
|
||||
if keyid := ctx.GlobalString(SwarmAccountFlag.Name); keyid != "" {
|
||||
currentConfig.BzzAccount = keyid
|
||||
}
|
||||
|
||||
if chbookaddr := ctx.GlobalString(ChequebookAddrFlag.Name); chbookaddr != "" {
|
||||
currentConfig.Contract = common.HexToAddress(chbookaddr)
|
||||
}
|
||||
|
||||
if networkid := ctx.GlobalString(SwarmNetworkIdFlag.Name); networkid != "" {
|
||||
id, err := strconv.ParseUint(networkid, 10, 64)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid cli flag %s: %v", SwarmNetworkIdFlag.Name, err)
|
||||
}
|
||||
if id != 0 {
|
||||
currentConfig.NetworkID = id
|
||||
}
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
|
||||
if datadir := ctx.GlobalString(utils.DataDirFlag.Name); datadir != "" {
|
||||
currentConfig.Path = expandPath(datadir)
|
||||
}
|
||||
}
|
||||
|
||||
bzzport := ctx.GlobalString(SwarmPortFlag.Name)
|
||||
if len(bzzport) > 0 {
|
||||
currentConfig.Port = bzzport
|
||||
}
|
||||
|
||||
if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" {
|
||||
currentConfig.ListenAddr = bzzaddr
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(SwarmSwapEnabledFlag.Name) {
|
||||
currentConfig.SwapEnabled = true
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(SwarmSyncDisabledFlag.Name) {
|
||||
currentConfig.SyncEnabled = false
|
||||
}
|
||||
|
||||
if d := ctx.GlobalDuration(SwarmSyncUpdateDelay.Name); d > 0 {
|
||||
currentConfig.SyncUpdateDelay = d
|
||||
}
|
||||
|
||||
// any value including 0 is acceptable
|
||||
currentConfig.MaxStreamPeerServers = ctx.GlobalInt(SwarmMaxStreamPeerServersFlag.Name)
|
||||
|
||||
if ctx.GlobalIsSet(SwarmLightNodeEnabled.Name) {
|
||||
currentConfig.LightNodeEnabled = true
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(SwarmDeliverySkipCheckFlag.Name) {
|
||||
currentConfig.DeliverySkipCheck = true
|
||||
}
|
||||
|
||||
currentConfig.SwapAPI = ctx.GlobalString(SwarmSwapAPIFlag.Name)
|
||||
if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
|
||||
utils.Fatalf(SwarmErrSwapSetNoAPI)
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(EnsAPIFlag.Name) {
|
||||
ensAPIs := ctx.GlobalStringSlice(EnsAPIFlag.Name)
|
||||
// preserve backward compatibility to disable ENS with --ens-api=""
|
||||
if len(ensAPIs) == 1 && ensAPIs[0] == "" {
|
||||
ensAPIs = nil
|
||||
}
|
||||
for i := range ensAPIs {
|
||||
ensAPIs[i] = expandPath(ensAPIs[i])
|
||||
}
|
||||
|
||||
currentConfig.EnsAPIs = ensAPIs
|
||||
}
|
||||
|
||||
if cors := ctx.GlobalString(CorsStringFlag.Name); cors != "" {
|
||||
currentConfig.Cors = cors
|
||||
}
|
||||
|
||||
if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" {
|
||||
currentConfig.ChunkDbPath = storePath
|
||||
}
|
||||
|
||||
if storeCapacity := ctx.GlobalUint64(SwarmStoreCapacity.Name); storeCapacity != 0 {
|
||||
currentConfig.DbCapacity = storeCapacity
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(SwarmStoreCacheCapacity.Name) {
|
||||
currentConfig.CacheCapacity = ctx.GlobalUint(SwarmStoreCacheCapacity.Name)
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(SwarmBootnodeModeFlag.Name) {
|
||||
currentConfig.BootnodeMode = ctx.GlobalBool(SwarmBootnodeModeFlag.Name)
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(SwarmGlobalStoreAPIFlag.Name) {
|
||||
currentConfig.GlobalStoreAPI = ctx.GlobalString(SwarmGlobalStoreAPIFlag.Name)
|
||||
}
|
||||
|
||||
return currentConfig
|
||||
|
||||
}
|
||||
|
||||
// envVarsOverride overrides the current config with whatver is provided in environment variables
|
||||
// most values are not allowed a zero value (empty string), if not otherwise noted
|
||||
func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
|
||||
if keyid := os.Getenv(SwarmEnvAccount); keyid != "" {
|
||||
currentConfig.BzzAccount = keyid
|
||||
}
|
||||
|
||||
if chbookaddr := os.Getenv(SwarmEnvChequebookAddr); chbookaddr != "" {
|
||||
currentConfig.Contract = common.HexToAddress(chbookaddr)
|
||||
}
|
||||
|
||||
if networkid := os.Getenv(SwarmEnvNetworkID); networkid != "" {
|
||||
id, err := strconv.ParseUint(networkid, 10, 64)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvNetworkID, err)
|
||||
}
|
||||
if id != 0 {
|
||||
currentConfig.NetworkID = id
|
||||
}
|
||||
}
|
||||
|
||||
if datadir := os.Getenv(GethEnvDataDir); datadir != "" {
|
||||
currentConfig.Path = expandPath(datadir)
|
||||
}
|
||||
|
||||
bzzport := os.Getenv(SwarmEnvPort)
|
||||
if len(bzzport) > 0 {
|
||||
currentConfig.Port = bzzport
|
||||
}
|
||||
|
||||
if bzzaddr := os.Getenv(SwarmEnvListenAddr); bzzaddr != "" {
|
||||
currentConfig.ListenAddr = bzzaddr
|
||||
}
|
||||
|
||||
if swapenable := os.Getenv(SwarmEnvSwapEnable); swapenable != "" {
|
||||
swap, err := strconv.ParseBool(swapenable)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSwapEnable, err)
|
||||
}
|
||||
currentConfig.SwapEnabled = swap
|
||||
}
|
||||
|
||||
if syncdisable := os.Getenv(SwarmEnvSyncDisable); syncdisable != "" {
|
||||
sync, err := strconv.ParseBool(syncdisable)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSyncDisable, err)
|
||||
}
|
||||
currentConfig.SyncEnabled = !sync
|
||||
}
|
||||
|
||||
if v := os.Getenv(SwarmEnvDeliverySkipCheck); v != "" {
|
||||
skipCheck, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
currentConfig.DeliverySkipCheck = skipCheck
|
||||
}
|
||||
}
|
||||
|
||||
if v := os.Getenv(SwarmEnvSyncUpdateDelay); v != "" {
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvSyncUpdateDelay, err)
|
||||
}
|
||||
currentConfig.SyncUpdateDelay = d
|
||||
}
|
||||
|
||||
if max := os.Getenv(SwarmEnvMaxStreamPeerServers); max != "" {
|
||||
m, err := strconv.Atoi(max)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvMaxStreamPeerServers, err)
|
||||
}
|
||||
currentConfig.MaxStreamPeerServers = m
|
||||
}
|
||||
|
||||
if lne := os.Getenv(SwarmEnvLightNodeEnable); lne != "" {
|
||||
lightnode, err := strconv.ParseBool(lne)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvLightNodeEnable, err)
|
||||
}
|
||||
currentConfig.LightNodeEnabled = lightnode
|
||||
}
|
||||
|
||||
if swapapi := os.Getenv(SwarmEnvSwapAPI); swapapi != "" {
|
||||
currentConfig.SwapAPI = swapapi
|
||||
}
|
||||
|
||||
if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" {
|
||||
utils.Fatalf(SwarmErrSwapSetNoAPI)
|
||||
}
|
||||
|
||||
if ensapi := os.Getenv(SwarmEnvENSAPI); ensapi != "" {
|
||||
currentConfig.EnsAPIs = strings.Split(ensapi, ",")
|
||||
}
|
||||
|
||||
if ensaddr := os.Getenv(SwarmEnvENSAddr); ensaddr != "" {
|
||||
currentConfig.EnsRoot = common.HexToAddress(ensaddr)
|
||||
}
|
||||
|
||||
if cors := os.Getenv(SwarmEnvCORS); cors != "" {
|
||||
currentConfig.Cors = cors
|
||||
}
|
||||
|
||||
if bm := os.Getenv(SwarmEnvBootnodeMode); bm != "" {
|
||||
bootnodeMode, err := strconv.ParseBool(bm)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmEnvBootnodeMode, err)
|
||||
}
|
||||
currentConfig.BootnodeMode = bootnodeMode
|
||||
}
|
||||
|
||||
if api := os.Getenv(SwarmGlobalstoreAPI); api != "" {
|
||||
currentConfig.GlobalStoreAPI = api
|
||||
}
|
||||
|
||||
return currentConfig
|
||||
}
|
||||
|
||||
// dumpConfig is the dumpconfig command.
|
||||
// writes a default config to STDOUT
|
||||
func dumpConfig(ctx *cli.Context) error {
|
||||
cfg, err := buildConfig(ctx)
|
||||
if err != nil {
|
||||
utils.Fatalf(fmt.Sprintf("Uh oh - dumpconfig triggered an error %v", err))
|
||||
}
|
||||
comment := ""
|
||||
out, err := tomlSettings.Marshal(&cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
io.WriteString(os.Stdout, comment)
|
||||
os.Stdout.Write(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
//validate configuration parameters
|
||||
func validateConfig(cfg *bzzapi.Config) (err error) {
|
||||
for _, ensAPI := range cfg.EnsAPIs {
|
||||
if ensAPI != "" {
|
||||
if err := validateEnsAPIs(ensAPI); err != nil {
|
||||
return fmt.Errorf("invalid format [tld:][contract-addr@]url for ENS API endpoint configuration %q: %v", ensAPI, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//validate EnsAPIs configuration parameter
|
||||
func validateEnsAPIs(s string) (err error) {
|
||||
// missing contract address
|
||||
if strings.HasPrefix(s, "@") {
|
||||
return errors.New("missing contract address")
|
||||
}
|
||||
// missing url
|
||||
if strings.HasSuffix(s, "@") {
|
||||
return errors.New("missing url")
|
||||
}
|
||||
// missing tld
|
||||
if strings.HasPrefix(s, ":") {
|
||||
return errors.New("missing tld")
|
||||
}
|
||||
// missing url
|
||||
if strings.HasSuffix(s, ":") {
|
||||
return errors.New("missing url")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//print a Config as string
|
||||
func printConfig(config *bzzapi.Config) string {
|
||||
out, err := tomlSettings.Marshal(&config)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("Something is not right with the configuration: %v", err)
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
|
@ -1,575 +0,0 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
)
|
||||
|
||||
func TestConfigDump(t *testing.T) {
|
||||
swarm := runSwarm(t, "dumpconfig")
|
||||
defaultConf := api.NewConfig()
|
||||
out, err := tomlSettings.Marshal(&defaultConf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
swarm.Expect(string(out))
|
||||
swarm.ExpectExit()
|
||||
}
|
||||
|
||||
func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
|
||||
fmt.Sprintf("--%s", SwarmSwapEnabledFlag.Name),
|
||||
}
|
||||
|
||||
swarm := runSwarm(t, flags...)
|
||||
swarm.Expect("Fatal: " + SwarmErrSwapSetNoAPI + "\n")
|
||||
swarm.ExpectExit()
|
||||
}
|
||||
|
||||
func TestConfigFailsNoBzzAccount(t *testing.T) {
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
|
||||
}
|
||||
|
||||
swarm := runSwarm(t, flags...)
|
||||
swarm.Expect("Fatal: " + SwarmErrNoBZZAccount + "\n")
|
||||
swarm.ExpectExit()
|
||||
}
|
||||
|
||||
func TestConfigCmdLineOverrides(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
conf, account := getTestAccount(t, dir)
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
// assign ports
|
||||
httpPort, err := assignTCPPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
|
||||
fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name),
|
||||
fmt.Sprintf("--%s", CorsStringFlag.Name), "*",
|
||||
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||
fmt.Sprintf("--%s", SwarmDeliverySkipCheckFlag.Name),
|
||||
fmt.Sprintf("--%s", EnsAPIFlag.Name), "",
|
||||
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
|
||||
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
|
||||
}
|
||||
node.Cmd = runSwarm(t, flags...)
|
||||
node.Cmd.InputLine(testPassphrase)
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
node.Shutdown()
|
||||
}
|
||||
}()
|
||||
// wait for the node to start
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if node.Client == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// load info
|
||||
var info swarm.Info
|
||||
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if info.Port != httpPort {
|
||||
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||
}
|
||||
|
||||
if info.NetworkID != 42 {
|
||||
t.Fatalf("Expected network ID to be %d, got %d", 42, info.NetworkID)
|
||||
}
|
||||
|
||||
if info.SyncEnabled {
|
||||
t.Fatal("Expected Sync to be disabled, but is true")
|
||||
}
|
||||
|
||||
if !info.DeliverySkipCheck {
|
||||
t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not")
|
||||
}
|
||||
|
||||
if info.Cors != "*" {
|
||||
t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors)
|
||||
}
|
||||
|
||||
node.Shutdown()
|
||||
}
|
||||
|
||||
func TestConfigFileOverrides(t *testing.T) {
|
||||
|
||||
// assign ports
|
||||
httpPort, err := assignTCPPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//create a config file
|
||||
//first, create a default conf
|
||||
defaultConf := api.NewConfig()
|
||||
//change some values in order to test if they have been loaded
|
||||
defaultConf.SyncEnabled = false
|
||||
defaultConf.DeliverySkipCheck = true
|
||||
defaultConf.NetworkID = 54
|
||||
defaultConf.Port = httpPort
|
||||
defaultConf.DbCapacity = 9000000
|
||||
defaultConf.HiveParams.KeepAliveInterval = 6000000000
|
||||
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
|
||||
//defaultConf.SyncParams.KeyBufferSize = 512
|
||||
//create a TOML string
|
||||
out, err := tomlSettings.Marshal(&defaultConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
//create file
|
||||
f, err := ioutil.TempFile("", "testconfig.toml")
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
//write file
|
||||
_, err = f.WriteString(string(out))
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
f.Sync()
|
||||
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
conf, account := getTestAccount(t, dir)
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(),
|
||||
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||
fmt.Sprintf("--%s", EnsAPIFlag.Name), "",
|
||||
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
|
||||
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
|
||||
}
|
||||
node.Cmd = runSwarm(t, flags...)
|
||||
node.Cmd.InputLine(testPassphrase)
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
node.Shutdown()
|
||||
}
|
||||
}()
|
||||
// wait for the node to start
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if node.Client == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// load info
|
||||
var info swarm.Info
|
||||
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if info.Port != httpPort {
|
||||
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||
}
|
||||
|
||||
if info.NetworkID != 54 {
|
||||
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkID)
|
||||
}
|
||||
|
||||
if info.SyncEnabled {
|
||||
t.Fatal("Expected Sync to be disabled, but is true")
|
||||
}
|
||||
|
||||
if !info.DeliverySkipCheck {
|
||||
t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not")
|
||||
}
|
||||
|
||||
if info.DbCapacity != 9000000 {
|
||||
t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkID)
|
||||
}
|
||||
|
||||
if info.HiveParams.KeepAliveInterval != 6000000000 {
|
||||
t.Fatalf("Expected HiveParams KeepAliveInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.KeepAliveInterval))
|
||||
}
|
||||
|
||||
if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second {
|
||||
t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval)
|
||||
}
|
||||
|
||||
// if info.SyncParams.KeyBufferSize != 512 {
|
||||
// t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize)
|
||||
// }
|
||||
|
||||
node.Shutdown()
|
||||
}
|
||||
|
||||
func TestConfigEnvVars(t *testing.T) {
|
||||
// assign ports
|
||||
httpPort, err := assignTCPPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
envVars := os.Environ()
|
||||
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmPortFlag.EnvVar, httpPort))
|
||||
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmNetworkIdFlag.EnvVar, "999"))
|
||||
envVars = append(envVars, fmt.Sprintf("%s=%s", CorsStringFlag.EnvVar, "*"))
|
||||
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmSyncDisabledFlag.EnvVar, "true"))
|
||||
envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmDeliverySkipCheckFlag.EnvVar, "true"))
|
||||
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
conf, account := getTestAccount(t, dir)
|
||||
node := &testNode{Dir: dir}
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||
"--ens-api", "",
|
||||
"--datadir", dir,
|
||||
"--ipcpath", conf.IPCPath,
|
||||
}
|
||||
|
||||
//node.Cmd = runSwarm(t,flags...)
|
||||
//node.Cmd.cmd.Env = envVars
|
||||
//the above assignment does not work, so we need a custom Cmd here in order to pass envVars:
|
||||
cmd := &exec.Cmd{
|
||||
Path: reexec.Self(),
|
||||
Args: append([]string{"swarm-test"}, flags...),
|
||||
Stderr: os.Stderr,
|
||||
Stdout: os.Stdout,
|
||||
}
|
||||
cmd.Env = envVars
|
||||
//stdout, err := cmd.StdoutPipe()
|
||||
//if err != nil {
|
||||
// t.Fatal(err)
|
||||
//}
|
||||
//stdout = bufio.NewReader(stdout)
|
||||
var stdin io.WriteCloser
|
||||
if stdin, err = cmd.StdinPipe(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//cmd.InputLine(testPassphrase)
|
||||
io.WriteString(stdin, testPassphrase+"\n")
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
node.Shutdown()
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}()
|
||||
// wait for the node to start
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if node.Client == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// load info
|
||||
var info swarm.Info
|
||||
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if info.Port != httpPort {
|
||||
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||
}
|
||||
|
||||
if info.NetworkID != 999 {
|
||||
t.Fatalf("Expected network ID to be %d, got %d", 999, info.NetworkID)
|
||||
}
|
||||
|
||||
if info.Cors != "*" {
|
||||
t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors)
|
||||
}
|
||||
|
||||
if info.SyncEnabled {
|
||||
t.Fatal("Expected Sync to be disabled, but is true")
|
||||
}
|
||||
|
||||
if !info.DeliverySkipCheck {
|
||||
t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not")
|
||||
}
|
||||
|
||||
node.Shutdown()
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
|
||||
func TestConfigCmdLineOverridesFile(t *testing.T) {
|
||||
|
||||
// assign ports
|
||||
httpPort, err := assignTCPPort()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//create a config file
|
||||
//first, create a default conf
|
||||
defaultConf := api.NewConfig()
|
||||
//change some values in order to test if they have been loaded
|
||||
defaultConf.SyncEnabled = true
|
||||
defaultConf.NetworkID = 54
|
||||
defaultConf.Port = "8588"
|
||||
defaultConf.DbCapacity = 9000000
|
||||
defaultConf.HiveParams.KeepAliveInterval = 6000000000
|
||||
defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second
|
||||
//defaultConf.SyncParams.KeyBufferSize = 512
|
||||
//create a TOML file
|
||||
out, err := tomlSettings.Marshal(&defaultConf)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
//write file
|
||||
fname := "testconfig.toml"
|
||||
f, err := ioutil.TempFile("", fname)
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
defer os.Remove(fname)
|
||||
//write file
|
||||
_, err = f.WriteString(string(out))
|
||||
if err != nil {
|
||||
t.Fatalf("Error writing TOML file in TestFileOverride: %v", err)
|
||||
}
|
||||
f.Sync()
|
||||
|
||||
dir, err := ioutil.TempDir("", "bzztest")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
conf, account := getTestAccount(t, dir)
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
expectNetworkId := uint64(77)
|
||||
|
||||
flags := []string{
|
||||
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "77",
|
||||
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
|
||||
fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name),
|
||||
fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(),
|
||||
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),
|
||||
fmt.Sprintf("--%s", EnsAPIFlag.Name), "",
|
||||
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
|
||||
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
|
||||
}
|
||||
node.Cmd = runSwarm(t, flags...)
|
||||
node.Cmd.InputLine(testPassphrase)
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
node.Shutdown()
|
||||
}
|
||||
}()
|
||||
// wait for the node to start
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if node.Client == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// load info
|
||||
var info swarm.Info
|
||||
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if info.Port != httpPort {
|
||||
t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port)
|
||||
}
|
||||
|
||||
if info.NetworkID != expectNetworkId {
|
||||
t.Fatalf("Expected network ID to be %d, got %d", expectNetworkId, info.NetworkID)
|
||||
}
|
||||
|
||||
if info.SyncEnabled {
|
||||
t.Fatal("Expected Sync to be disabled, but is true")
|
||||
}
|
||||
|
||||
if info.DbCapacity != 9000000 {
|
||||
t.Fatalf("Expected Capacity to be %d, got %d", 9000000, info.DbCapacity)
|
||||
}
|
||||
|
||||
if info.HiveParams.KeepAliveInterval != 6000000000 {
|
||||
t.Fatalf("Expected HiveParams KeepAliveInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.KeepAliveInterval))
|
||||
}
|
||||
|
||||
if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second {
|
||||
t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval)
|
||||
}
|
||||
|
||||
// if info.SyncParams.KeyBufferSize != 512 {
|
||||
// t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize)
|
||||
// }
|
||||
|
||||
node.Shutdown()
|
||||
}
|
||||
|
||||
func TestConfigValidate(t *testing.T) {
|
||||
for _, c := range []struct {
|
||||
cfg *api.Config
|
||||
err string
|
||||
}{
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"/data/testnet/geth.ipc",
|
||||
}},
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"http://127.0.0.1:1234",
|
||||
}},
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"ws://127.0.0.1:1234",
|
||||
}},
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"test:/data/testnet/geth.ipc",
|
||||
}},
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"test:ws://127.0.0.1:1234",
|
||||
}},
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/geth.ipc",
|
||||
}},
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234",
|
||||
}},
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:1234",
|
||||
}},
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"test:314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/geth.ipc",
|
||||
}},
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"eth:314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234",
|
||||
}},
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"eth:314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:12344",
|
||||
}},
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"eth:",
|
||||
}},
|
||||
err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"eth:\": missing url",
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"314159265dD8dbb310642f98f50C066173C1259b@",
|
||||
}},
|
||||
err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"314159265dD8dbb310642f98f50C066173C1259b@\": missing url",
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
":314159265dD8dbb310642f98f50C066173C1259",
|
||||
}},
|
||||
err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \":314159265dD8dbb310642f98f50C066173C1259\": missing tld",
|
||||
},
|
||||
{
|
||||
cfg: &api.Config{EnsAPIs: []string{
|
||||
"@/data/testnet/geth.ipc",
|
||||
}},
|
||||
err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"@/data/testnet/geth.ipc\": missing contract address",
|
||||
},
|
||||
} {
|
||||
err := validateConfig(c.cfg)
|
||||
if c.err != "" && err.Error() != c.err {
|
||||
t.Errorf("expected error %q, got %q", c.err, err)
|
||||
}
|
||||
if c.err == "" && err != nil {
|
||||
t.Errorf("unexpected error %q", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assignTCPPort() (string, error) {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
l.Close()
|
||||
_, port, err := net.SplitHostPort(l.Addr().String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return port, nil
|
||||
}
|
||||
239
cmd/swarm/db.go
239
cmd/swarm/db.go
|
|
@ -1,239 +0,0 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/localstore"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var legacyKeyIndex = byte(0)
|
||||
var keyData = byte(6)
|
||||
|
||||
type dpaDBIndex struct {
|
||||
Idx uint64
|
||||
Access uint64
|
||||
}
|
||||
|
||||
var dbCommand = cli.Command{
|
||||
Name: "db",
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Usage: "manage the local chunk database",
|
||||
ArgsUsage: "db COMMAND",
|
||||
Description: "Manage the local chunk database",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Action: dbExport,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "export",
|
||||
Usage: "export a local chunk database as a tar archive (use - to send to stdout)",
|
||||
ArgsUsage: "<chunkdb> <file>",
|
||||
Description: `
|
||||
Export a local chunk database as a tar archive (use - to send to stdout).
|
||||
|
||||
swarm db export ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
|
||||
|
||||
The export may be quite large, consider piping the output through the Unix
|
||||
pv(1) tool to get a progress bar:
|
||||
|
||||
swarm db export ~/.ethereum/swarm/bzz-KEY/chunks - | pv > chunks.tar
|
||||
`,
|
||||
},
|
||||
{
|
||||
Action: dbImport,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "import",
|
||||
Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
|
||||
ArgsUsage: "<chunkdb> <file>",
|
||||
Description: `Import chunks from a tar archive into a local chunk database (use - to read from stdin).
|
||||
|
||||
swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
|
||||
|
||||
The import may be quite large, consider piping the input through the Unix
|
||||
pv(1) tool to get a progress bar:
|
||||
|
||||
pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks -`,
|
||||
Flags: []cli.Flag{
|
||||
SwarmLegacyFlag,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func dbExport(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) != 3 {
|
||||
utils.Fatalf("invalid arguments, please specify both <chunkdb> (path to a local chunk database), <file> (path to write the tar archive to, - for stdout) and the base key")
|
||||
}
|
||||
|
||||
var out io.Writer
|
||||
if args[1] == "-" {
|
||||
out = os.Stdout
|
||||
} else {
|
||||
f, err := os.Create(args[1])
|
||||
if err != nil {
|
||||
utils.Fatalf("error opening output file: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
out = f
|
||||
}
|
||||
|
||||
isLegacy := localstore.IsLegacyDatabase(args[0])
|
||||
if isLegacy {
|
||||
count, err := exportLegacy(args[0], common.Hex2Bytes(args[2]), out)
|
||||
if err != nil {
|
||||
utils.Fatalf("error exporting legacy local chunk database: %s", err)
|
||||
}
|
||||
|
||||
log.Info(fmt.Sprintf("successfully exported %d chunks from legacy db", count))
|
||||
return
|
||||
}
|
||||
|
||||
store, err := openLDBStore(args[0], common.Hex2Bytes(args[2]))
|
||||
if err != nil {
|
||||
utils.Fatalf("error opening local chunk database: %s", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
count, err := store.Export(out)
|
||||
if err != nil {
|
||||
utils.Fatalf("error exporting local chunk database: %s", err)
|
||||
}
|
||||
|
||||
log.Info(fmt.Sprintf("successfully exported %d chunks", count))
|
||||
}
|
||||
|
||||
func dbImport(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) != 3 {
|
||||
utils.Fatalf("invalid arguments, please specify both <chunkdb> (path to a local chunk database), <file> (path to read the tar archive from, - for stdin) and the base key")
|
||||
}
|
||||
|
||||
legacy := ctx.IsSet(SwarmLegacyFlag.Name)
|
||||
|
||||
store, err := openLDBStore(args[0], common.Hex2Bytes(args[2]))
|
||||
if err != nil {
|
||||
utils.Fatalf("error opening local chunk database: %s", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
var in io.Reader
|
||||
if args[1] == "-" {
|
||||
in = os.Stdin
|
||||
} else {
|
||||
f, err := os.Open(args[1])
|
||||
if err != nil {
|
||||
utils.Fatalf("error opening input file: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
in = f
|
||||
}
|
||||
|
||||
count, err := store.Import(in, legacy)
|
||||
if err != nil {
|
||||
utils.Fatalf("error importing local chunk database: %s", err)
|
||||
}
|
||||
|
||||
log.Info(fmt.Sprintf("successfully imported %d chunks", count))
|
||||
}
|
||||
|
||||
func openLDBStore(path string, basekey []byte) (*localstore.DB, error) {
|
||||
if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
|
||||
return nil, fmt.Errorf("invalid chunkdb path: %s", err)
|
||||
}
|
||||
|
||||
return localstore.New(path, basekey, nil)
|
||||
}
|
||||
|
||||
func decodeIndex(data []byte, index *dpaDBIndex) error {
|
||||
dec := rlp.NewStream(bytes.NewReader(data), 0)
|
||||
return dec.Decode(index)
|
||||
}
|
||||
|
||||
func getDataKey(idx uint64, po uint8) []byte {
|
||||
key := make([]byte, 10)
|
||||
key[0] = keyData
|
||||
key[1] = po
|
||||
binary.BigEndian.PutUint64(key[2:], idx)
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
func exportLegacy(path string, basekey []byte, out io.Writer) (int64, error) {
|
||||
tw := tar.NewWriter(out)
|
||||
defer tw.Close()
|
||||
db, err := leveldb.OpenFile(path, &opt.Options{OpenFilesCacheCapacity: 128})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
it := db.NewIterator(nil, nil)
|
||||
defer it.Release()
|
||||
var count int64
|
||||
for ok := it.Seek([]byte{legacyKeyIndex}); ok; ok = it.Next() {
|
||||
key := it.Key()
|
||||
if (key == nil) || (key[0] != legacyKeyIndex) {
|
||||
break
|
||||
}
|
||||
|
||||
var index dpaDBIndex
|
||||
|
||||
hash := key[1:]
|
||||
decodeIndex(it.Value(), &index)
|
||||
|
||||
po := uint8(chunk.Proximity(basekey, hash))
|
||||
|
||||
datakey := getDataKey(index.Idx, po)
|
||||
data, err := db.Get(datakey, nil)
|
||||
if err != nil {
|
||||
log.Crit(fmt.Sprintf("Chunk %x found but could not be accessed: %v, %x", key, err, datakey))
|
||||
continue
|
||||
}
|
||||
|
||||
hdr := &tar.Header{
|
||||
Name: hex.EncodeToString(hash),
|
||||
Mode: 0644,
|
||||
Size: int64(len(data)),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return count, err
|
||||
}
|
||||
if _, err := tw.Write(data); err != nil {
|
||||
return count, err
|
||||
}
|
||||
count++
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
|
@ -1,112 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var downloadCommand = cli.Command{
|
||||
Action: download,
|
||||
Name: "down",
|
||||
Flags: []cli.Flag{SwarmRecursiveFlag, SwarmAccessPasswordFlag},
|
||||
Usage: "downloads a swarm manifest or a file inside a manifest",
|
||||
ArgsUsage: " <uri> [<dir>]",
|
||||
Description: `Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.`,
|
||||
}
|
||||
|
||||
func download(ctx *cli.Context) {
|
||||
log.Debug("downloading content using swarm down")
|
||||
args := ctx.Args()
|
||||
dest := "."
|
||||
|
||||
switch len(args) {
|
||||
case 0:
|
||||
utils.Fatalf("Usage: swarm down [options] <bzz locator> [<destination path>]")
|
||||
case 1:
|
||||
log.Trace(fmt.Sprintf("swarm down: no destination path - assuming working dir"))
|
||||
default:
|
||||
log.Trace(fmt.Sprintf("destination path arg: %s", args[1]))
|
||||
if absDest, err := filepath.Abs(args[1]); err == nil {
|
||||
dest = absDest
|
||||
} else {
|
||||
utils.Fatalf("could not get download path: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
isRecursive = ctx.Bool(SwarmRecursiveFlag.Name)
|
||||
client = swarm.NewClient(bzzapi)
|
||||
)
|
||||
|
||||
if fi, err := os.Stat(dest); err == nil {
|
||||
if isRecursive && !fi.Mode().IsDir() {
|
||||
utils.Fatalf("destination path is not a directory!")
|
||||
}
|
||||
} else {
|
||||
if !os.IsNotExist(err) {
|
||||
utils.Fatalf("could not stat path: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
uri, err := api.Parse(args[0])
|
||||
if err != nil {
|
||||
utils.Fatalf("could not parse uri argument: %v", err)
|
||||
}
|
||||
|
||||
dl := func(credentials string) error {
|
||||
// assume behaviour according to --recursive switch
|
||||
if isRecursive {
|
||||
if err := client.DownloadDirectory(uri.Addr, uri.Path, dest, credentials); err != nil {
|
||||
if err == swarm.ErrUnauthorized {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("directory %s: %v", uri.Path, err)
|
||||
}
|
||||
} else {
|
||||
// we are downloading a file
|
||||
log.Debug("downloading file/path from a manifest", "uri.Addr", uri.Addr, "uri.Path", uri.Path)
|
||||
|
||||
err := client.DownloadFile(uri.Addr, uri.Path, dest, credentials)
|
||||
if err != nil {
|
||||
if err == swarm.ErrUnauthorized {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("file %s from address: %s: %v", uri.Path, uri.Addr, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if passwords := makePasswordList(ctx); passwords != nil {
|
||||
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), 0, passwords)
|
||||
err = dl(password)
|
||||
} else {
|
||||
err = dl("")
|
||||
}
|
||||
if err != nil {
|
||||
utils.Fatalf("download: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Command bzzhash computes a swarm tree hash.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var hashesCommand = cli.Command{
|
||||
Action: hashes,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "hashes",
|
||||
Usage: "print all hashes of a file to STDOUT",
|
||||
ArgsUsage: "<file>",
|
||||
Description: "Prints all hashes of a file to STDOUT",
|
||||
}
|
||||
|
||||
func hashes(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) < 1 {
|
||||
utils.Fatalf("Usage: swarm hashes <file name>")
|
||||
}
|
||||
f, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
utils.Fatalf("Error opening file " + args[1])
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
fileStore := storage.NewFileStore(&storage.FakeChunkStore{}, storage.NewFileStoreParams(), chunk.NewTags())
|
||||
refs, err := fileStore.GetAllReferences(context.TODO(), f, false)
|
||||
if err != nil {
|
||||
utils.Fatalf("%v\n", err)
|
||||
} else {
|
||||
for _, r := range refs {
|
||||
fmt.Println(r.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,287 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/swarm/testdata"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
)
|
||||
|
||||
const (
|
||||
DATABASE_FIXTURE_BZZ_ACCOUNT = "0aa159029fa13ffa8fa1c6fff6ebceface99d6a4"
|
||||
DATABASE_FIXTURE_PASSWORD = "pass"
|
||||
FIXTURE_DATADIR_PREFIX = "swarm/bzz-0aa159029fa13ffa8fa1c6fff6ebceface99d6a4"
|
||||
FixtureBaseKey = "a9f22b3d77b4bdf5f3eefce995d6c8e7cecf2636f20956f08a0d1ed95adb52ad"
|
||||
)
|
||||
|
||||
// TestCLISwarmExportImport perform the following test:
|
||||
// 1. runs swarm node
|
||||
// 2. uploads a random file
|
||||
// 3. runs an export of the local datastore
|
||||
// 4. runs a second swarm node
|
||||
// 5. imports the exported datastore
|
||||
// 6. fetches the uploaded random file from the second node
|
||||
func TestCLISwarmExportImport(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
cluster := newTestCluster(t, 1)
|
||||
|
||||
// generate random 1mb file
|
||||
content := testutil.RandomBytes(1, 1000000)
|
||||
fileName := testutil.TempFileWithContent(t, string(content))
|
||||
defer os.Remove(fileName)
|
||||
|
||||
// upload the file with 'swarm up' and expect a hash
|
||||
up := runSwarm(t, "--bzzapi", cluster.Nodes[0].URL, "up", fileName)
|
||||
_, matches := up.ExpectRegexp(`[a-f\d]{64}`)
|
||||
up.ExpectExit()
|
||||
hash := matches[0]
|
||||
|
||||
var info swarm.Info
|
||||
if err := cluster.Nodes[0].Client.Call(&info, "bzz_info"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cluster.Stop()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
// generate an export.tar
|
||||
exportCmd := runSwarm(t, "db", "export", info.Path+"/chunks", info.Path+"/export.tar", strings.TrimPrefix(info.BzzKey, "0x"))
|
||||
exportCmd.ExpectExit()
|
||||
|
||||
// start second cluster
|
||||
cluster2 := newTestCluster(t, 1)
|
||||
|
||||
var info2 swarm.Info
|
||||
if err := cluster2.Nodes[0].Client.Call(&info2, "bzz_info"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// stop second cluster, so that we close LevelDB
|
||||
cluster2.Stop()
|
||||
defer cluster2.Cleanup()
|
||||
|
||||
// import the export.tar
|
||||
importCmd := runSwarm(t, "db", "import", info2.Path+"/chunks", info.Path+"/export.tar", strings.TrimPrefix(info2.BzzKey, "0x"))
|
||||
importCmd.ExpectExit()
|
||||
|
||||
// spin second cluster back up
|
||||
cluster2.StartExistingNodes(t, 1, strings.TrimPrefix(info2.BzzAccount, "0x"))
|
||||
|
||||
// try to fetch imported file
|
||||
res, err := http.Get(cluster2.Nodes[0].URL + "/bzz:/" + hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
t.Fatalf("expected HTTP status %d, got %s", 200, res.Status)
|
||||
}
|
||||
|
||||
// compare downloaded file with the generated random file
|
||||
mustEqualFiles(t, bytes.NewReader(content), res.Body)
|
||||
}
|
||||
|
||||
// TestExportLegacyToNew checks that an old database gets imported correctly into the new localstore structure
|
||||
// The test sequence is as follows:
|
||||
// 1. unpack database fixture to tmp dir
|
||||
// 2. try to open with new swarm binary that should complain about old database
|
||||
// 3. export from old database
|
||||
// 4. remove the chunks folder
|
||||
// 5. import the dump
|
||||
// 6. file should be accessible
|
||||
func TestExportLegacyToNew(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip() // this should be reenabled once the appveyor tests underlying issue is fixed
|
||||
}
|
||||
/*
|
||||
fixture bzz account 0aa159029fa13ffa8fa1c6fff6ebceface99d6a4
|
||||
*/
|
||||
const UPLOADED_FILE_MD5_HASH = "a001fdae53ba50cae584b8b02b06f821"
|
||||
const UPLOADED_HASH = "67a86082ee0ea1bc7dd8d955bb1e14d04f61d55ae6a4b37b3d0296a3a95e454a"
|
||||
tmpdir, err := ioutil.TempDir("", "swarm-test")
|
||||
log.Trace("running legacy datastore migration test", "temp dir", tmpdir)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
inflateBase64Gzip(t, testdata.DATADIR_MIGRATION_FIXTURE, tmpdir)
|
||||
|
||||
tmpPassword := testutil.TempFileWithContent(t, DATABASE_FIXTURE_PASSWORD)
|
||||
defer os.Remove(tmpPassword)
|
||||
|
||||
flags := []string{
|
||||
"--datadir", tmpdir,
|
||||
"--bzzaccount", DATABASE_FIXTURE_BZZ_ACCOUNT,
|
||||
"--password", tmpPassword,
|
||||
}
|
||||
|
||||
newSwarmOldDb := runSwarm(t, flags...)
|
||||
_, matches := newSwarmOldDb.ExpectRegexp(".+")
|
||||
newSwarmOldDb.ExpectExit()
|
||||
|
||||
if len(matches) == 0 {
|
||||
t.Fatalf("stdout not matched")
|
||||
}
|
||||
|
||||
if newSwarmOldDb.ExitStatus() == 0 {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
t.Log("exporting legacy database")
|
||||
actualDataDir := path.Join(tmpdir, FIXTURE_DATADIR_PREFIX)
|
||||
exportCmd := runSwarm(t, "--verbosity", "5", "db", "export", actualDataDir+"/chunks", tmpdir+"/export.tar", FixtureBaseKey)
|
||||
exportCmd.ExpectExit()
|
||||
|
||||
stat, err := os.Stat(tmpdir + "/export.tar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make some silly size assumption
|
||||
if stat.Size() < 90000 {
|
||||
t.Fatal("export size too small")
|
||||
}
|
||||
log.Info("removing chunk datadir")
|
||||
err = os.RemoveAll(path.Join(actualDataDir, "chunks"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// start second cluster
|
||||
cluster2 := newTestCluster(t, 1)
|
||||
var info2 swarm.Info
|
||||
if err := cluster2.Nodes[0].Client.Call(&info2, "bzz_info"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// stop second cluster, so that we close LevelDB
|
||||
cluster2.Stop()
|
||||
defer cluster2.Cleanup()
|
||||
|
||||
// import the export.tar
|
||||
importCmd := runSwarm(t, "db", "import", "--legacy", info2.Path+"/chunks", tmpdir+"/export.tar", strings.TrimPrefix(info2.BzzKey, "0x"))
|
||||
importCmd.ExpectExit()
|
||||
|
||||
// spin second cluster back up
|
||||
cluster2.StartExistingNodes(t, 1, strings.TrimPrefix(info2.BzzAccount, "0x"))
|
||||
t.Log("trying to http get the file")
|
||||
// try to fetch imported file
|
||||
res, err := http.Get(cluster2.Nodes[0].URL + "/bzz:/" + UPLOADED_HASH)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
t.Fatalf("expected HTTP status %d, got %s", 200, res.Status)
|
||||
}
|
||||
h := md5.New()
|
||||
if _, err := io.Copy(h, res.Body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sum := h.Sum(nil)
|
||||
|
||||
b, err := hex.DecodeString(UPLOADED_FILE_MD5_HASH)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(sum, b) {
|
||||
t.Fatal("should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
func mustEqualFiles(t *testing.T, up io.Reader, down io.Reader) {
|
||||
h := md5.New()
|
||||
upLen, err := io.Copy(h, up)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
upHash := h.Sum(nil)
|
||||
h.Reset()
|
||||
downLen, err := io.Copy(h, down)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
downHash := h.Sum(nil)
|
||||
|
||||
if !bytes.Equal(upHash, downHash) || upLen != downLen {
|
||||
t.Fatalf("downloaded imported file md5=%x (length %v) is not the same as the generated one mp5=%x (length %v)", downHash, downLen, upHash, upLen)
|
||||
}
|
||||
}
|
||||
|
||||
func inflateBase64Gzip(t *testing.T, base64File, directory string) {
|
||||
t.Helper()
|
||||
|
||||
f := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64File))
|
||||
gzf, err := gzip.NewReader(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tarReader := tar.NewReader(gzf)
|
||||
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
name := header.Name
|
||||
|
||||
switch header.Typeflag {
|
||||
case tar.TypeDir:
|
||||
err := os.Mkdir(path.Join(directory, name), os.ModePerm)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
case tar.TypeReg:
|
||||
file, err := os.Create(path.Join(directory, name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := io.Copy(file, tarReader); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file.Close()
|
||||
default:
|
||||
t.Fatal("shouldn't happen")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,238 +0,0 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Command feed allows the user to create and update signed Swarm feeds
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var feedCommand = cli.Command{
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "feed",
|
||||
Usage: "(Advanced) Create and update Swarm Feeds",
|
||||
ArgsUsage: "<create|update|info>",
|
||||
Description: "Works with Swarm Feeds",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Action: feedCreateManifest,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "create",
|
||||
Usage: "creates and publishes a new feed manifest",
|
||||
Description: `creates and publishes a new feed manifest pointing to a specified user's updates about a particular topic.
|
||||
The feed topic can be built in the following ways:
|
||||
* use --topic to set the topic to an arbitrary binary hex string.
|
||||
* use --name to set the topic to a human-readable name.
|
||||
For example --name could be set to "profile-picture", meaning this feed allows to get this user's current profile picture.
|
||||
* use both --topic and --name to create named subtopics.
|
||||
For example, --topic could be set to an Ethereum contract address and --name could be set to "comments", meaning
|
||||
this feed tracks a discussion about that contract.
|
||||
The --user flag allows to have this manifest refer to a user other than yourself. If not specified,
|
||||
it will then default to your local account (--bzzaccount)`,
|
||||
Flags: []cli.Flag{SwarmFeedNameFlag, SwarmFeedTopicFlag, SwarmFeedUserFlag},
|
||||
},
|
||||
{
|
||||
Action: feedUpdate,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "update",
|
||||
Usage: "updates the content of an existing Swarm Feed",
|
||||
ArgsUsage: "<0x Hex data>",
|
||||
Description: `publishes a new update on the specified topic
|
||||
The feed topic can be built in the following ways:
|
||||
* use --topic to set the topic to an arbitrary binary hex string.
|
||||
* use --name to set the topic to a human-readable name.
|
||||
For example --name could be set to "profile-picture", meaning this feed allows to get this user's current profile picture.
|
||||
* use both --topic and --name to create named subtopics.
|
||||
For example, --topic could be set to an Ethereum contract address and --name could be set to "comments", meaning
|
||||
this feed tracks a discussion about that contract.
|
||||
|
||||
If you have a manifest, you can specify it with --manifest to refer to the feed,
|
||||
instead of using --topic / --name
|
||||
`,
|
||||
Flags: []cli.Flag{SwarmFeedManifestFlag, SwarmFeedNameFlag, SwarmFeedTopicFlag},
|
||||
},
|
||||
{
|
||||
Action: feedInfo,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "info",
|
||||
Usage: "obtains information about an existing Swarm feed",
|
||||
Description: `obtains information about an existing Swarm feed
|
||||
The topic can be specified directly with the --topic flag as an hex string
|
||||
If no topic is specified, the default topic (zero) will be used
|
||||
The --name flag can be used to specify subtopics with a specific name.
|
||||
The --user flag allows to refer to a user other than yourself. If not specified,
|
||||
it will then default to your local account (--bzzaccount)
|
||||
If you have a manifest, you can specify it with --manifest instead of --topic / --name / ---user
|
||||
to refer to the feed`,
|
||||
Flags: []cli.Flag{SwarmFeedManifestFlag, SwarmFeedNameFlag, SwarmFeedTopicFlag, SwarmFeedUserFlag},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func NewGenericSigner(ctx *cli.Context) feed.Signer {
|
||||
return feed.NewGenericSigner(getPrivKey(ctx))
|
||||
}
|
||||
|
||||
func getTopic(ctx *cli.Context) (topic feed.Topic) {
|
||||
var name = ctx.String(SwarmFeedNameFlag.Name)
|
||||
var relatedTopic = ctx.String(SwarmFeedTopicFlag.Name)
|
||||
var relatedTopicBytes []byte
|
||||
var err error
|
||||
|
||||
if relatedTopic != "" {
|
||||
relatedTopicBytes, err = hexutil.Decode(relatedTopic)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error parsing topic: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
topic, err = feed.NewTopic(name, relatedTopicBytes)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error parsing topic: %s", err)
|
||||
}
|
||||
return topic
|
||||
}
|
||||
|
||||
// swarm feed create <frequency> [--name <name>] [--data <0x Hexdata> [--multihash=false]]
|
||||
// swarm feed update <Manifest Address or ENS domain> <0x Hexdata> [--multihash=false]
|
||||
// swarm feed info <Manifest Address or ENS domain>
|
||||
|
||||
func feedCreateManifest(ctx *cli.Context) {
|
||||
var (
|
||||
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
client = swarm.NewClient(bzzapi)
|
||||
)
|
||||
|
||||
newFeedUpdateRequest := feed.NewFirstRequest(getTopic(ctx))
|
||||
newFeedUpdateRequest.Feed.User = feedGetUser(ctx)
|
||||
|
||||
manifestAddress, err := client.CreateFeedWithManifest(newFeedUpdateRequest)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error creating feed manifest: %s", err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println(manifestAddress) // output manifest address to the user in a single line (useful for other commands to pick up)
|
||||
|
||||
}
|
||||
|
||||
func feedUpdate(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
|
||||
var (
|
||||
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
client = swarm.NewClient(bzzapi)
|
||||
manifestAddressOrDomain = ctx.String(SwarmFeedManifestFlag.Name)
|
||||
)
|
||||
|
||||
if len(args) < 1 {
|
||||
fmt.Println("Incorrect number of arguments")
|
||||
cli.ShowCommandHelpAndExit(ctx, "update", 1)
|
||||
return
|
||||
}
|
||||
|
||||
signer := NewGenericSigner(ctx)
|
||||
|
||||
data, err := hexutil.Decode(args[0])
|
||||
if err != nil {
|
||||
utils.Fatalf("Error parsing data: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var updateRequest *feed.Request
|
||||
var query *feed.Query
|
||||
|
||||
if manifestAddressOrDomain == "" {
|
||||
query = new(feed.Query)
|
||||
query.User = signer.Address()
|
||||
query.Topic = getTopic(ctx)
|
||||
}
|
||||
|
||||
// Retrieve a feed update request
|
||||
updateRequest, err = client.GetFeedRequest(query, manifestAddressOrDomain)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error retrieving feed status: %s", err.Error())
|
||||
}
|
||||
|
||||
// Check that the provided signer matches the request to sign
|
||||
if updateRequest.User != signer.Address() {
|
||||
utils.Fatalf("Signer address does not match the update request")
|
||||
}
|
||||
|
||||
// set the new data
|
||||
updateRequest.SetData(data)
|
||||
|
||||
// sign update
|
||||
if err = updateRequest.Sign(signer); err != nil {
|
||||
utils.Fatalf("Error signing feed update: %s", err.Error())
|
||||
}
|
||||
|
||||
// post update
|
||||
err = client.UpdateFeed(updateRequest)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error updating feed: %s", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func feedInfo(ctx *cli.Context) {
|
||||
var (
|
||||
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
client = swarm.NewClient(bzzapi)
|
||||
manifestAddressOrDomain = ctx.String(SwarmFeedManifestFlag.Name)
|
||||
)
|
||||
|
||||
var query *feed.Query
|
||||
if manifestAddressOrDomain == "" {
|
||||
query = new(feed.Query)
|
||||
query.Topic = getTopic(ctx)
|
||||
query.User = feedGetUser(ctx)
|
||||
}
|
||||
|
||||
metadata, err := client.GetFeedRequest(query, manifestAddressOrDomain)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error retrieving feed metadata: %s", err.Error())
|
||||
return
|
||||
}
|
||||
encodedMetadata, err := metadata.MarshalJSON()
|
||||
if err != nil {
|
||||
utils.Fatalf("Error encoding metadata to JSON for display:%s", err)
|
||||
}
|
||||
fmt.Println(string(encodedMetadata))
|
||||
}
|
||||
|
||||
func feedGetUser(ctx *cli.Context) common.Address {
|
||||
var user = ctx.String(SwarmFeedUserFlag.Name)
|
||||
if user != "" {
|
||||
return common.HexToAddress(user)
|
||||
}
|
||||
pk := getPrivKey(ctx)
|
||||
if pk == nil {
|
||||
utils.Fatalf("Cannot read private key. Must specify --user or --bzzaccount")
|
||||
}
|
||||
return crypto.PubkeyToAddress(pk.PublicKey)
|
||||
|
||||
}
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
)
|
||||
|
||||
func TestCLIFeedUpdate(t *testing.T) {
|
||||
srv := swarmhttp.NewTestSwarmServer(t, func(api *api.API) swarmhttp.TestServer {
|
||||
return swarmhttp.NewServer(api, "")
|
||||
}, nil)
|
||||
log.Info("starting a test swarm server")
|
||||
defer srv.Close()
|
||||
|
||||
// create a private key file for signing
|
||||
privkeyHex := "0000000000000000000000000000000000000000000000000000000000001979"
|
||||
privKey, _ := crypto.HexToECDSA(privkeyHex)
|
||||
address := crypto.PubkeyToAddress(privKey.PublicKey)
|
||||
|
||||
pkFileName := testutil.TempFileWithContent(t, privkeyHex)
|
||||
defer os.Remove(pkFileName)
|
||||
|
||||
// compose a topic. We'll be doing quotes about Miguel de Cervantes
|
||||
var topic feed.Topic
|
||||
subject := []byte("Miguel de Cervantes")
|
||||
copy(topic[:], subject[:])
|
||||
name := "quotes"
|
||||
|
||||
// prepare some data for the update
|
||||
data := []byte("En boca cerrada no entran moscas")
|
||||
hexData := hexutil.Encode(data)
|
||||
|
||||
flags := []string{
|
||||
"--bzzapi", srv.URL,
|
||||
"--bzzaccount", pkFileName,
|
||||
"feed", "update",
|
||||
"--topic", topic.Hex(),
|
||||
"--name", name,
|
||||
hexData}
|
||||
|
||||
// create an update and expect an exit without errors
|
||||
log.Info("updating a feed with 'swarm feed update'")
|
||||
cmd := runSwarm(t, flags...)
|
||||
cmd.ExpectExit()
|
||||
|
||||
// now try to get the update using the client
|
||||
client := swarm.NewClient(srv.URL)
|
||||
|
||||
// build the same topic as before, this time
|
||||
// we use NewTopic to create a topic automatically.
|
||||
topic, err := feed.NewTopic(name, subject)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Feed configures whose updates we will be looking up.
|
||||
fd := feed.Feed{
|
||||
Topic: topic,
|
||||
User: address,
|
||||
}
|
||||
|
||||
// Build a query to get the latest update
|
||||
query := feed.NewQueryLatest(&fd, lookup.NoClue)
|
||||
|
||||
// retrieve content!
|
||||
reader, err := client.QueryFeed(query, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
retrieved, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// check we retrieved the sent information
|
||||
if !bytes.Equal(data, retrieved) {
|
||||
t.Fatalf("Received %s, expected %s", retrieved, data)
|
||||
}
|
||||
|
||||
// Now retrieve info for the next update
|
||||
flags = []string{
|
||||
"--bzzapi", srv.URL,
|
||||
"feed", "info",
|
||||
"--topic", topic.Hex(),
|
||||
"--user", address.Hex(),
|
||||
}
|
||||
|
||||
log.Info("getting feed info with 'swarm feed info'")
|
||||
cmd = runSwarm(t, flags...)
|
||||
_, matches := cmd.ExpectRegexp(`.*`) // regex hack to extract stdout
|
||||
cmd.ExpectExit()
|
||||
|
||||
// verify we can deserialize the result as a valid JSON
|
||||
var request feed.Request
|
||||
err = json.Unmarshal([]byte(matches[0]), &request)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make sure the retrieved feed is the same
|
||||
if request.Feed != fd {
|
||||
t.Fatalf("Expected feed to be: %s, got %s", fd, request.Feed)
|
||||
}
|
||||
|
||||
// test publishing a manifest
|
||||
flags = []string{
|
||||
"--bzzapi", srv.URL,
|
||||
"--bzzaccount", pkFileName,
|
||||
"feed", "create",
|
||||
"--topic", topic.Hex(),
|
||||
}
|
||||
|
||||
log.Info("Publishing manifest with 'swarm feed create'")
|
||||
cmd = runSwarm(t, flags...)
|
||||
_, matches = cmd.ExpectRegexp(`[a-f\d]{64}`)
|
||||
cmd.ExpectExit()
|
||||
|
||||
manifestAddress := matches[0] // read the received feed manifest
|
||||
|
||||
// now attempt to lookup the latest update using a manifest instead
|
||||
reader, err = client.QueryFeed(nil, manifestAddress)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
retrieved, err = ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(data, retrieved) {
|
||||
t.Fatalf("Received %s, expected %s", retrieved, data)
|
||||
}
|
||||
|
||||
// test publishing a manifest for a different user
|
||||
flags = []string{
|
||||
"--bzzapi", srv.URL,
|
||||
"feed", "create",
|
||||
"--topic", topic.Hex(),
|
||||
"--user", "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // different user
|
||||
}
|
||||
|
||||
log.Info("Publishing manifest with 'swarm feed create' for a different user")
|
||||
cmd = runSwarm(t, flags...)
|
||||
_, matches = cmd.ExpectRegexp(`[a-f\d]{64}`)
|
||||
cmd.ExpectExit()
|
||||
|
||||
manifestAddress = matches[0] // read the received feed manifest
|
||||
|
||||
// now let's try to update that user's manifest which we don't have the private key for
|
||||
flags = []string{
|
||||
"--bzzapi", srv.URL,
|
||||
"--bzzaccount", pkFileName,
|
||||
"feed", "update",
|
||||
"--manifest", manifestAddress,
|
||||
hexData}
|
||||
|
||||
// create an update and expect an error given there is a user mismatch
|
||||
log.Info("updating a feed with 'swarm feed update'")
|
||||
cmd = runSwarm(t, flags...)
|
||||
cmd.ExpectRegexp("Fatal:.*") // best way so far to detect a failure.
|
||||
cmd.ExpectExit()
|
||||
if cmd.ExitStatus() == 0 {
|
||||
t.Fatal("Expected nonzero exit code when updating a manifest with the wrong user. Got 0.")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Command feed allows the user to create and update signed Swarm feeds
|
||||
package main
|
||||
|
||||
import cli "gopkg.in/urfave/cli.v1"
|
||||
|
||||
var (
|
||||
ChequebookAddrFlag = cli.StringFlag{
|
||||
Name: "chequebook",
|
||||
Usage: "chequebook contract address",
|
||||
EnvVar: SwarmEnvChequebookAddr,
|
||||
}
|
||||
SwarmAccountFlag = cli.StringFlag{
|
||||
Name: "bzzaccount",
|
||||
Usage: "Swarm account key file",
|
||||
EnvVar: SwarmEnvAccount,
|
||||
}
|
||||
SwarmListenAddrFlag = cli.StringFlag{
|
||||
Name: "httpaddr",
|
||||
Usage: "Swarm HTTP API listening interface",
|
||||
EnvVar: SwarmEnvListenAddr,
|
||||
}
|
||||
SwarmPortFlag = cli.StringFlag{
|
||||
Name: "bzzport",
|
||||
Usage: "Swarm local http api port",
|
||||
EnvVar: SwarmEnvPort,
|
||||
}
|
||||
SwarmNetworkIdFlag = cli.IntFlag{
|
||||
Name: "bzznetworkid",
|
||||
Usage: "Network identifier (integer, default 3=swarm testnet)",
|
||||
EnvVar: SwarmEnvNetworkID,
|
||||
}
|
||||
SwarmSwapEnabledFlag = cli.BoolFlag{
|
||||
Name: "swap",
|
||||
Usage: "Swarm SWAP enabled (default false)",
|
||||
EnvVar: SwarmEnvSwapEnable,
|
||||
}
|
||||
SwarmSwapAPIFlag = cli.StringFlag{
|
||||
Name: "swap-api",
|
||||
Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
|
||||
EnvVar: SwarmEnvSwapAPI,
|
||||
}
|
||||
SwarmSyncDisabledFlag = cli.BoolTFlag{
|
||||
Name: "nosync",
|
||||
Usage: "Disable swarm syncing",
|
||||
EnvVar: SwarmEnvSyncDisable,
|
||||
}
|
||||
SwarmSyncUpdateDelay = cli.DurationFlag{
|
||||
Name: "sync-update-delay",
|
||||
Usage: "Duration for sync subscriptions update after no new peers are added (default 15s)",
|
||||
EnvVar: SwarmEnvSyncUpdateDelay,
|
||||
}
|
||||
SwarmMaxStreamPeerServersFlag = cli.IntFlag{
|
||||
Name: "max-stream-peer-servers",
|
||||
Usage: "Limit of Stream peer servers, 0 denotes unlimited",
|
||||
EnvVar: SwarmEnvMaxStreamPeerServers,
|
||||
Value: 10000, // A very large default value is possible as stream servers have very small memory footprint
|
||||
}
|
||||
SwarmLightNodeEnabled = cli.BoolFlag{
|
||||
Name: "lightnode",
|
||||
Usage: "Enable Swarm LightNode (default false)",
|
||||
EnvVar: SwarmEnvLightNodeEnable,
|
||||
}
|
||||
SwarmDeliverySkipCheckFlag = cli.BoolFlag{
|
||||
Name: "delivery-skip-check",
|
||||
Usage: "Skip chunk delivery check (default false)",
|
||||
EnvVar: SwarmEnvDeliverySkipCheck,
|
||||
}
|
||||
EnsAPIFlag = cli.StringSliceFlag{
|
||||
Name: "ens-api",
|
||||
Usage: "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url",
|
||||
EnvVar: SwarmEnvENSAPI,
|
||||
}
|
||||
SwarmApiFlag = cli.StringFlag{
|
||||
Name: "bzzapi",
|
||||
Usage: "Specifies the Swarm HTTP endpoint to connect to",
|
||||
Value: "http://127.0.0.1:8500",
|
||||
}
|
||||
SwarmRecursiveFlag = cli.BoolFlag{
|
||||
Name: "recursive",
|
||||
Usage: "Upload directories recursively",
|
||||
}
|
||||
SwarmWantManifestFlag = cli.BoolTFlag{
|
||||
Name: "manifest",
|
||||
Usage: "Automatic manifest upload (default true)",
|
||||
}
|
||||
SwarmUploadDefaultPath = cli.StringFlag{
|
||||
Name: "defaultpath",
|
||||
Usage: "path to file served for empty url path (none)",
|
||||
}
|
||||
SwarmAccessGrantKeyFlag = cli.StringFlag{
|
||||
Name: "grant-key",
|
||||
Usage: "grants a given public key access to an ACT",
|
||||
}
|
||||
SwarmAccessGrantKeysFlag = cli.StringFlag{
|
||||
Name: "grant-keys",
|
||||
Usage: "grants a given list of public keys in the following file (separated by line breaks) access to an ACT",
|
||||
}
|
||||
SwarmUpFromStdinFlag = cli.BoolFlag{
|
||||
Name: "stdin",
|
||||
Usage: "reads data to be uploaded from stdin",
|
||||
}
|
||||
SwarmUploadMimeType = cli.StringFlag{
|
||||
Name: "mime",
|
||||
Usage: "Manually specify MIME type",
|
||||
}
|
||||
SwarmEncryptedFlag = cli.BoolFlag{
|
||||
Name: "encrypt",
|
||||
Usage: "use encrypted upload",
|
||||
}
|
||||
SwarmAccessPasswordFlag = cli.StringFlag{
|
||||
Name: "password",
|
||||
Usage: "Password",
|
||||
EnvVar: SwarmAccessPassword,
|
||||
}
|
||||
SwarmDryRunFlag = cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "dry-run",
|
||||
}
|
||||
CorsStringFlag = cli.StringFlag{
|
||||
Name: "corsdomain",
|
||||
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
||||
EnvVar: SwarmEnvCORS,
|
||||
}
|
||||
SwarmStorePath = cli.StringFlag{
|
||||
Name: "store.path",
|
||||
Usage: "Path to leveldb chunk DB (default <$GETH_ENV_DIR>/swarm/bzz-<$BZZ_KEY>/chunks)",
|
||||
EnvVar: SwarmEnvStorePath,
|
||||
}
|
||||
SwarmStoreCapacity = cli.Uint64Flag{
|
||||
Name: "store.size",
|
||||
Usage: "Number of chunks (5M is roughly 20-25GB) (default 5000000)",
|
||||
EnvVar: SwarmEnvStoreCapacity,
|
||||
}
|
||||
SwarmStoreCacheCapacity = cli.UintFlag{
|
||||
Name: "store.cache.size",
|
||||
Usage: "Number of recent chunks cached in memory",
|
||||
EnvVar: SwarmEnvStoreCacheCapacity,
|
||||
Value: 10000,
|
||||
}
|
||||
SwarmCompressedFlag = cli.BoolFlag{
|
||||
Name: "compressed",
|
||||
Usage: "Prints encryption keys in compressed form",
|
||||
}
|
||||
SwarmBootnodeModeFlag = cli.BoolFlag{
|
||||
Name: "bootnode-mode",
|
||||
Usage: "Run Swarm in Bootnode mode",
|
||||
}
|
||||
SwarmFeedNameFlag = cli.StringFlag{
|
||||
Name: "name",
|
||||
Usage: "User-defined name for the new feed, limited to 32 characters. If combined with topic, it will refer to a subtopic with this name",
|
||||
}
|
||||
SwarmFeedTopicFlag = cli.StringFlag{
|
||||
Name: "topic",
|
||||
Usage: "User-defined topic this feed is tracking, hex encoded. Limited to 64 hexadecimal characters",
|
||||
}
|
||||
SwarmFeedManifestFlag = cli.StringFlag{
|
||||
Name: "manifest",
|
||||
Usage: "Refers to the feed through a manifest",
|
||||
}
|
||||
SwarmFeedUserFlag = cli.StringFlag{
|
||||
Name: "user",
|
||||
Usage: "Indicates the user who updates the feed",
|
||||
}
|
||||
SwarmGlobalStoreAPIFlag = cli.StringFlag{
|
||||
Name: "globalstore-api",
|
||||
Usage: "URL of the Global Store API provider (only for testing)",
|
||||
EnvVar: SwarmGlobalstoreAPI,
|
||||
}
|
||||
SwarmLegacyFlag = cli.BoolFlag{
|
||||
Name: "legacy",
|
||||
Usage: "Use this flag when importing a db export from a legacy local store database dump (for schemas older than 'sanctuary')",
|
||||
}
|
||||
)
|
||||
162
cmd/swarm/fs.go
162
cmd/swarm/fs.go
|
|
@ -1,162 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm/fuse"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var fsCommand = cli.Command{
|
||||
Name: "fs",
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Usage: "perform FUSE operations",
|
||||
ArgsUsage: "fs COMMAND",
|
||||
Description: "Performs FUSE operations by mounting/unmounting/listing mount points. This assumes you already have a Swarm node running locally. For all operation you must reference the correct path to bzzd.ipc in order to communicate with the node",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Action: mount,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "mount",
|
||||
Usage: "mount a swarm hash to a mount point",
|
||||
ArgsUsage: "swarm fs mount <manifest hash> <mount point>",
|
||||
Description: "Mounts a Swarm manifest hash to a given mount point. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
|
||||
},
|
||||
{
|
||||
Action: unmount,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "unmount",
|
||||
Usage: "unmount a swarmfs mount",
|
||||
ArgsUsage: "swarm fs unmount <mount point>",
|
||||
Description: "Unmounts a swarmfs mount residing at <mount point>. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
|
||||
},
|
||||
{
|
||||
Action: listMounts,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "list",
|
||||
Usage: "list swarmfs mounts",
|
||||
ArgsUsage: "swarm fs list",
|
||||
Description: "Lists all mounted swarmfs volumes. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func mount(cliContext *cli.Context) {
|
||||
args := cliContext.Args()
|
||||
if len(args) < 2 {
|
||||
utils.Fatalf("Usage: swarm fs mount <manifestHash> <file name>")
|
||||
}
|
||||
|
||||
client, err := dialRPC(cliContext)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error dailing to RPC endpoint: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mf := &fuse.MountInfo{}
|
||||
mountPoint, err := filepath.Abs(filepath.Clean(args[1]))
|
||||
if err != nil {
|
||||
utils.Fatalf("error expanding path for mount point: %v", err)
|
||||
}
|
||||
err = client.CallContext(ctx, mf, "swarmfs_mount", args[0], mountPoint)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error calling the RPC endpoint while mounting: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func unmount(cliContext *cli.Context) {
|
||||
args := cliContext.Args()
|
||||
|
||||
if len(args) < 1 {
|
||||
utils.Fatalf("Usage: swarm fs unmount <mount path>")
|
||||
}
|
||||
client, err := dialRPC(cliContext)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error dailing to RPC endpoint: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mf := fuse.MountInfo{}
|
||||
err = client.CallContext(ctx, &mf, "swarmfs_unmount", args[0])
|
||||
if err != nil {
|
||||
utils.Fatalf("encountered an error calling the RPC endpoint while unmounting: %v", err)
|
||||
}
|
||||
fmt.Printf("%s\n", mf.LatestManifest) //print the latest manifest hash for user reference
|
||||
}
|
||||
|
||||
func listMounts(cliContext *cli.Context) {
|
||||
client, err := dialRPC(cliContext)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error dailing to RPC endpoint: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var mf []fuse.MountInfo
|
||||
err = client.CallContext(ctx, &mf, "swarmfs_listmounts")
|
||||
if err != nil {
|
||||
utils.Fatalf("encountered an error calling the RPC endpoint while listing mounts: %v", err)
|
||||
}
|
||||
if len(mf) == 0 {
|
||||
fmt.Print("Could not found any swarmfs mounts. Please make sure you've specified the correct RPC endpoint\n")
|
||||
} else {
|
||||
fmt.Printf("Found %d swarmfs mount(s):\n", len(mf))
|
||||
for i, mountInfo := range mf {
|
||||
fmt.Printf("%d:\n", i)
|
||||
fmt.Printf("\tMount point: %s\n", mountInfo.MountPoint)
|
||||
fmt.Printf("\tLatest Manifest: %s\n", mountInfo.LatestManifest)
|
||||
fmt.Printf("\tStart Manifest: %s\n", mountInfo.StartManifest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dialRPC(ctx *cli.Context) (*rpc.Client, error) {
|
||||
endpoint := getIPCEndpoint(ctx)
|
||||
log.Info("IPC endpoint", "path", endpoint)
|
||||
return rpc.Dial(endpoint)
|
||||
}
|
||||
|
||||
func getIPCEndpoint(ctx *cli.Context) string {
|
||||
cfg := defaultNodeConfig
|
||||
utils.SetNodeConfig(ctx, &cfg)
|
||||
|
||||
endpoint := cfg.IPCEndpoint()
|
||||
|
||||
if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") {
|
||||
// Backwards compatibility with geth < 1.5 which required
|
||||
// these prefixes.
|
||||
endpoint = endpoint[4:]
|
||||
}
|
||||
return endpoint
|
||||
}
|
||||
|
|
@ -1,260 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build linux freebsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
type testFile struct {
|
||||
filePath string
|
||||
content string
|
||||
}
|
||||
|
||||
// TestCLISwarmFsDefaultIPCPath tests if the most basic fs command, i.e., list
|
||||
// can find and correctly connect to a running Swarm node on the default
|
||||
// IPCPath.
|
||||
func TestCLISwarmFsDefaultIPCPath(t *testing.T) {
|
||||
cluster := newTestCluster(t, 1)
|
||||
defer cluster.Shutdown()
|
||||
|
||||
handlingNode := cluster.Nodes[0]
|
||||
list := runSwarm(t, []string{
|
||||
"--datadir", handlingNode.Dir,
|
||||
"fs",
|
||||
"list",
|
||||
}...)
|
||||
|
||||
list.WaitExit()
|
||||
if list.Err != nil {
|
||||
t.Fatal(list.Err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCLISwarmFs is a high-level test of swarmfs
|
||||
//
|
||||
// This test fails on travis for macOS as this executable exits with code 1
|
||||
// and without any log messages in the log:
|
||||
// /Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse.
|
||||
// This is the reason for this file not being built on darwin architecture.
|
||||
func TestCLISwarmFs(t *testing.T) {
|
||||
cluster := newTestCluster(t, 3)
|
||||
defer cluster.Shutdown()
|
||||
|
||||
// create a tmp dir
|
||||
mountPoint, err := ioutil.TempDir("", "swarm-test")
|
||||
log.Debug("swarmfs cli test", "1st mount", mountPoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(mountPoint)
|
||||
|
||||
handlingNode := cluster.Nodes[0]
|
||||
mhash := doUploadEmptyDir(t, handlingNode)
|
||||
log.Debug("swarmfs cli test: mounting first run", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
|
||||
|
||||
mount := runSwarm(t, []string{
|
||||
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
|
||||
"fs",
|
||||
"mount",
|
||||
mhash,
|
||||
mountPoint,
|
||||
}...)
|
||||
mount.ExpectExit()
|
||||
|
||||
filesToAssert := []*testFile{}
|
||||
|
||||
dirPath, err := createDirInDir(mountPoint, "testSubDir")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dirPath2, err := createDirInDir(dirPath, "AnotherTestSubDir")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dummyContent := "somerandomtestcontentthatshouldbeasserted"
|
||||
dirs := []string{
|
||||
mountPoint,
|
||||
dirPath,
|
||||
dirPath2,
|
||||
}
|
||||
files := []string{"f1.tmp", "f2.tmp"}
|
||||
for _, d := range dirs {
|
||||
for _, entry := range files {
|
||||
tFile, err := createTestFileInPath(d, entry, dummyContent)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
filesToAssert = append(filesToAssert, tFile)
|
||||
}
|
||||
}
|
||||
if len(filesToAssert) != len(dirs)*len(files) {
|
||||
t.Fatalf("should have %d files to assert now, got %d", len(dirs)*len(files), len(filesToAssert))
|
||||
}
|
||||
hashRegexp := `[a-f\d]{64}`
|
||||
log.Debug("swarmfs cli test: unmounting first run...", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
|
||||
|
||||
unmount := runSwarm(t, []string{
|
||||
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
|
||||
"fs",
|
||||
"unmount",
|
||||
mountPoint,
|
||||
}...)
|
||||
_, matches := unmount.ExpectRegexp(hashRegexp)
|
||||
unmount.ExpectExit()
|
||||
|
||||
hash := matches[0]
|
||||
if hash == mhash {
|
||||
t.Fatal("this should not be equal")
|
||||
}
|
||||
log.Debug("swarmfs cli test: asserting no files in mount point")
|
||||
|
||||
//check that there's nothing in the mount folder
|
||||
filesInDir, err := ioutil.ReadDir(mountPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("had an error reading the directory: %v", err)
|
||||
}
|
||||
|
||||
if len(filesInDir) != 0 {
|
||||
t.Fatal("there shouldn't be anything here")
|
||||
}
|
||||
|
||||
secondMountPoint, err := ioutil.TempDir("", "swarm-test")
|
||||
log.Debug("swarmfs cli test", "2nd mount point at", secondMountPoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(secondMountPoint)
|
||||
|
||||
log.Debug("swarmfs cli test: remounting at second mount point", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
|
||||
|
||||
//remount, check files
|
||||
newMount := runSwarm(t, []string{
|
||||
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
|
||||
"fs",
|
||||
"mount",
|
||||
hash, // the latest hash
|
||||
secondMountPoint,
|
||||
}...)
|
||||
|
||||
newMount.ExpectExit()
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
filesInDir, err = ioutil.ReadDir(secondMountPoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(filesInDir) == 0 {
|
||||
t.Fatal("there should be something here")
|
||||
}
|
||||
|
||||
log.Debug("swarmfs cli test: traversing file tree to see it matches previous mount")
|
||||
|
||||
for _, file := range filesToAssert {
|
||||
file.filePath = strings.Replace(file.filePath, mountPoint, secondMountPoint, -1)
|
||||
fileBytes, err := ioutil.ReadFile(file.filePath)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(fileBytes, bytes.NewBufferString(file.content).Bytes()) {
|
||||
t.Fatal("this should be equal")
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("swarmfs cli test: unmounting second run", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath))
|
||||
|
||||
unmountSec := runSwarm(t, []string{
|
||||
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath),
|
||||
"fs",
|
||||
"unmount",
|
||||
secondMountPoint,
|
||||
}...)
|
||||
|
||||
_, matches = unmountSec.ExpectRegexp(hashRegexp)
|
||||
unmountSec.ExpectExit()
|
||||
|
||||
if matches[0] != hash {
|
||||
t.Fatal("these should be equal - no changes made")
|
||||
}
|
||||
}
|
||||
|
||||
func doUploadEmptyDir(t *testing.T, node *testNode) string {
|
||||
// create a tmp dir
|
||||
tmpDir, err := ioutil.TempDir("", "swarm-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
hashRegexp := `[a-f\d]{64}`
|
||||
|
||||
flags := []string{
|
||||
"--bzzapi", node.URL,
|
||||
"--recursive",
|
||||
"up",
|
||||
tmpDir}
|
||||
|
||||
log.Info("swarmfs cli test: uploading dir with 'swarm up'")
|
||||
up := runSwarm(t, flags...)
|
||||
_, matches := up.ExpectRegexp(hashRegexp)
|
||||
up.ExpectExit()
|
||||
hash := matches[0]
|
||||
log.Info("swarmfs cli test: dir uploaded", "hash", hash)
|
||||
return hash
|
||||
}
|
||||
|
||||
func createDirInDir(createInDir string, dirToCreate string) (string, error) {
|
||||
fullpath := filepath.Join(createInDir, dirToCreate)
|
||||
err := os.MkdirAll(fullpath, 0777)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fullpath, nil
|
||||
}
|
||||
|
||||
func createTestFileInPath(dir, filename, content string) (*testFile, error) {
|
||||
tFile := &testFile{}
|
||||
filePath := filepath.Join(dir, filename)
|
||||
if file, err := os.Create(filePath); err == nil {
|
||||
tFile.content = content
|
||||
tFile.filePath = filePath
|
||||
|
||||
_, err = io.WriteString(file, content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
|
||||
return tFile, nil
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/mock"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/mock/explorer"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// serveChunkExplorer starts an http server in background with chunk explorer handler
|
||||
// using the provided global store. Server is started if the returned shutdown function
|
||||
// is not nil.
|
||||
func serveChunkExplorer(ctx *cli.Context, globalStore mock.GlobalStorer) (shutdown func(), err error) {
|
||||
if !ctx.IsSet("explorer-address") {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
corsOrigins := ctx.StringSlice("explorer-cors-origin")
|
||||
server := &http.Server{
|
||||
Handler: explorer.NewHandler(globalStore, corsOrigins),
|
||||
IdleTimeout: 30 * time.Minute,
|
||||
ReadTimeout: 2 * time.Minute,
|
||||
WriteTimeout: 2 * time.Minute,
|
||||
}
|
||||
listener, err := net.Listen("tcp", ctx.String("explorer-address"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("explorer: %v", err)
|
||||
}
|
||||
log.Info("chunk explorer http", "address", listener.Addr().String(), "origins", corsOrigins)
|
||||
|
||||
go func() {
|
||||
if err := server.Serve(listener); err != nil {
|
||||
log.Error("chunk explorer", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := server.Shutdown(ctx); err != nil {
|
||||
log.Error("chunk explorer: shutdown", "err", err)
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/mock/explorer"
|
||||
mockRPC "github.com/ethereum/go-ethereum/swarm/storage/mock/rpc"
|
||||
)
|
||||
|
||||
// TestExplorer validates basic chunk explorer functionality by storing
|
||||
// a small set of chunk and making http requests on exposed endpoint.
|
||||
// Full chunk explorer validation is done in mock/explorer package.
|
||||
func TestExplorer(t *testing.T) {
|
||||
addr := findFreeTCPAddress(t)
|
||||
explorerAddr := findFreeTCPAddress(t)
|
||||
testCmd := runGlobalStore(t, "ws", "--addr", addr, "--explorer-address", explorerAddr)
|
||||
defer testCmd.Kill()
|
||||
|
||||
client := websocketClient(t, addr)
|
||||
|
||||
store := mockRPC.NewGlobalStore(client)
|
||||
defer store.Close()
|
||||
|
||||
nodeKeys := map[string][]string{
|
||||
"a1": {"b1", "b2", "b3"},
|
||||
"a2": {"b3", "b4", "b5"},
|
||||
}
|
||||
|
||||
keyNodes := make(map[string][]string)
|
||||
|
||||
for addr, keys := range nodeKeys {
|
||||
for _, key := range keys {
|
||||
keyNodes[key] = append(keyNodes[key], addr)
|
||||
}
|
||||
}
|
||||
|
||||
invalidAddr := "c1"
|
||||
invalidKey := "d1"
|
||||
|
||||
for addr, keys := range nodeKeys {
|
||||
for _, key := range keys {
|
||||
err := store.Put(common.HexToAddress(addr), common.Hex2Bytes(key), []byte("data"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endpoint := "http://" + explorerAddr
|
||||
|
||||
t.Run("has key", func(t *testing.T) {
|
||||
for addr, keys := range nodeKeys {
|
||||
for _, key := range keys {
|
||||
testStatusResponse(t, endpoint+"/api/has-key/"+addr+"/"+key, http.StatusOK)
|
||||
testStatusResponse(t, endpoint+"/api/has-key/"+invalidAddr+"/"+key, http.StatusNotFound)
|
||||
}
|
||||
testStatusResponse(t, endpoint+"/api/has-key/"+addr+"/"+invalidKey, http.StatusNotFound)
|
||||
}
|
||||
testStatusResponse(t, endpoint+"/api/has-key/"+invalidAddr+"/"+invalidKey, http.StatusNotFound)
|
||||
})
|
||||
|
||||
t.Run("keys", func(t *testing.T) {
|
||||
var keys []string
|
||||
for key := range keyNodes {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
testKeysResponse(t, endpoint+"/api/keys", explorer.KeysResponse{
|
||||
Keys: keys,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("nodes", func(t *testing.T) {
|
||||
var nodes []string
|
||||
for addr := range nodeKeys {
|
||||
nodes = append(nodes, common.HexToAddress(addr).Hex())
|
||||
}
|
||||
sort.Strings(nodes)
|
||||
testNodesResponse(t, endpoint+"/api/nodes", explorer.NodesResponse{
|
||||
Nodes: nodes,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("node keys", func(t *testing.T) {
|
||||
for addr, keys := range nodeKeys {
|
||||
testKeysResponse(t, endpoint+"/api/keys?node="+addr, explorer.KeysResponse{
|
||||
Keys: keys,
|
||||
})
|
||||
}
|
||||
testKeysResponse(t, endpoint+"/api/keys?node="+invalidAddr, explorer.KeysResponse{})
|
||||
})
|
||||
|
||||
t.Run("key nodes", func(t *testing.T) {
|
||||
for key, addrs := range keyNodes {
|
||||
var nodes []string
|
||||
for _, addr := range addrs {
|
||||
nodes = append(nodes, common.HexToAddress(addr).Hex())
|
||||
}
|
||||
sort.Strings(nodes)
|
||||
testNodesResponse(t, endpoint+"/api/nodes?key="+key, explorer.NodesResponse{
|
||||
Nodes: nodes,
|
||||
})
|
||||
}
|
||||
testNodesResponse(t, endpoint+"/api/nodes?key="+invalidKey, explorer.NodesResponse{})
|
||||
})
|
||||
}
|
||||
|
||||
// TestExplorer_CORSOrigin validates if chunk explorer returns
|
||||
// correct CORS origin header in GET and OPTIONS requests.
|
||||
func TestExplorer_CORSOrigin(t *testing.T) {
|
||||
origin := "http://localhost/"
|
||||
addr := findFreeTCPAddress(t)
|
||||
explorerAddr := findFreeTCPAddress(t)
|
||||
testCmd := runGlobalStore(t, "ws",
|
||||
"--addr", addr,
|
||||
"--explorer-address", explorerAddr,
|
||||
"--explorer-cors-origin", origin,
|
||||
)
|
||||
defer testCmd.Kill()
|
||||
|
||||
// wait until the server is started
|
||||
waitHTTPEndpoint(t, explorerAddr)
|
||||
|
||||
url := "http://" + explorerAddr + "/api/keys"
|
||||
|
||||
t.Run("get", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("Origin", origin)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
header := resp.Header.Get("Access-Control-Allow-Origin")
|
||||
if header != origin {
|
||||
t.Errorf("got Access-Control-Allow-Origin header %q, want %q", header, origin)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("preflight", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodOptions, url, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("Origin", origin)
|
||||
req.Header.Set("Access-Control-Request-Method", "GET")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
header := resp.Header.Get("Access-Control-Allow-Origin")
|
||||
if header != origin {
|
||||
t.Errorf("got Access-Control-Allow-Origin header %q, want %q", header, origin)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// testStatusResponse makes an http request to provided url
|
||||
// and validates if response is explorer.StatusResponse for
|
||||
// the expected status code.
|
||||
func testStatusResponse(t *testing.T, url string, code int) {
|
||||
t.Helper()
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.StatusCode != code {
|
||||
t.Errorf("got status code %v, want %v", resp.StatusCode, code)
|
||||
}
|
||||
var r explorer.StatusResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.Code != code {
|
||||
t.Errorf("got response code %v, want %v", r.Code, code)
|
||||
}
|
||||
if r.Message != http.StatusText(code) {
|
||||
t.Errorf("got response message %q, want %q", r.Message, http.StatusText(code))
|
||||
}
|
||||
}
|
||||
|
||||
// testKeysResponse makes an http request to provided url
|
||||
// and validates if response machhes expected explorer.KeysResponse.
|
||||
func testKeysResponse(t *testing.T, url string, want explorer.KeysResponse) {
|
||||
t.Helper()
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("got status code %v, want %v", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
var r explorer.KeysResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if fmt.Sprint(r.Keys) != fmt.Sprint(want.Keys) {
|
||||
t.Errorf("got keys %v, want %v", r.Keys, want.Keys)
|
||||
}
|
||||
if r.Next != want.Next {
|
||||
t.Errorf("got next %s, want %s", r.Next, want.Next)
|
||||
}
|
||||
}
|
||||
|
||||
// testNodeResponse makes an http request to provided url
|
||||
// and validates if response machhes expected explorer.NodeResponse.
|
||||
func testNodesResponse(t *testing.T, url string, want explorer.NodesResponse) {
|
||||
t.Helper()
|
||||
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("got status code %v, want %v", resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
var r explorer.NodesResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&r); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if fmt.Sprint(r.Nodes) != fmt.Sprint(want.Nodes) {
|
||||
t.Errorf("got nodes %v, want %v", r.Nodes, want.Nodes)
|
||||
}
|
||||
if r.Next != want.Next {
|
||||
t.Errorf("got next %s, want %s", r.Next, want.Next)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/mock"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/mock/db"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/mock/mem"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// startHTTP starts a global store with HTTP RPC server.
|
||||
// It is used for "http" cli command.
|
||||
func startHTTP(ctx *cli.Context) (err error) {
|
||||
server, cleanup, err := newServer(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
listener, err := net.Listen("tcp", ctx.String("addr"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("http", "address", listener.Addr().String())
|
||||
|
||||
return http.Serve(listener, server)
|
||||
}
|
||||
|
||||
// startWS starts a global store with WebSocket RPC server.
|
||||
// It is used for "websocket" cli command.
|
||||
func startWS(ctx *cli.Context) (err error) {
|
||||
server, cleanup, err := newServer(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cleanup()
|
||||
|
||||
listener, err := net.Listen("tcp", ctx.String("addr"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
origins := ctx.StringSlice("origins")
|
||||
log.Info("websocket", "address", listener.Addr().String(), "origins", origins)
|
||||
|
||||
return http.Serve(listener, server.WebsocketHandler(origins))
|
||||
}
|
||||
|
||||
// newServer creates a global store and starts a chunk explorer server if configured.
|
||||
// Returned cleanup function should be called only if err is nil.
|
||||
func newServer(ctx *cli.Context) (server *rpc.Server, cleanup func(), err error) {
|
||||
log.PrintOrigins(true)
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(ctx.Int("verbosity")), log.StreamHandler(os.Stdout, log.TerminalFormat(false))))
|
||||
|
||||
cleanup = func() {}
|
||||
var globalStore mock.GlobalStorer
|
||||
dir := ctx.String("dir")
|
||||
if dir != "" {
|
||||
dbStore, err := db.NewGlobalStore(dir)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cleanup = func() {
|
||||
if err := dbStore.Close(); err != nil {
|
||||
log.Error("global store: close", "err", err)
|
||||
}
|
||||
}
|
||||
globalStore = dbStore
|
||||
log.Info("database global store", "dir", dir)
|
||||
} else {
|
||||
globalStore = mem.NewGlobalStore()
|
||||
log.Info("in-memory global store")
|
||||
}
|
||||
|
||||
server = rpc.NewServer()
|
||||
if err := server.RegisterName("mockStore", globalStore); err != nil {
|
||||
cleanup()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
shutdown, err := serveChunkExplorer(ctx, globalStore)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return nil, nil, err
|
||||
}
|
||||
if shutdown != nil {
|
||||
cleanup = func() {
|
||||
shutdown()
|
||||
|
||||
if c, ok := globalStore.(io.Closer); ok {
|
||||
if err := c.Close(); err != nil {
|
||||
log.Error("global store: close", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return server, cleanup, nil
|
||||
}
|
||||
|
|
@ -1,207 +0,0 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
mockRPC "github.com/ethereum/go-ethereum/swarm/storage/mock/rpc"
|
||||
)
|
||||
|
||||
// TestHTTP_InMemory tests in-memory global store that exposes
|
||||
// HTTP server.
|
||||
func TestHTTP_InMemory(t *testing.T) {
|
||||
testHTTP(t, true)
|
||||
}
|
||||
|
||||
// TestHTTP_Database tests global store with persisted database
|
||||
// that exposes HTTP server.
|
||||
func TestHTTP_Database(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "swarm-global-store-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// create a fresh global store
|
||||
testHTTP(t, true, "--dir", dir)
|
||||
|
||||
// check if data saved by the previous global store instance
|
||||
testHTTP(t, false, "--dir", dir)
|
||||
}
|
||||
|
||||
// testWebsocket starts global store binary with HTTP server
|
||||
// and validates that it can store and retrieve data.
|
||||
// If put is false, no data will be stored, only retrieved,
|
||||
// giving the possibility to check if data is present in the
|
||||
// storage directory.
|
||||
func testHTTP(t *testing.T, put bool, args ...string) {
|
||||
addr := findFreeTCPAddress(t)
|
||||
testCmd := runGlobalStore(t, append([]string{"http", "--addr", addr}, args...)...)
|
||||
defer testCmd.Kill()
|
||||
|
||||
client, err := rpc.DialHTTP("http://" + addr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// wait until global store process is started as
|
||||
// rpc.DialHTTP is actually not connecting
|
||||
waitHTTPEndpoint(t, addr)
|
||||
|
||||
store := mockRPC.NewGlobalStore(client)
|
||||
defer store.Close()
|
||||
|
||||
node := store.NewNodeStore(common.HexToAddress("123abc"))
|
||||
|
||||
wantKey := "key"
|
||||
wantValue := "value"
|
||||
|
||||
if put {
|
||||
err = node.Put([]byte(wantKey), []byte(wantValue))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
gotValue, err := node.Get([]byte(wantKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(gotValue) != wantValue {
|
||||
t.Errorf("got value %s for key %s, want %s", string(gotValue), wantKey, wantValue)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWebsocket_InMemory tests in-memory global store that exposes
|
||||
// WebSocket server.
|
||||
func TestWebsocket_InMemory(t *testing.T) {
|
||||
testWebsocket(t, true)
|
||||
}
|
||||
|
||||
// TestWebsocket_Database tests global store with persisted database
|
||||
// that exposes HTTP server.
|
||||
func TestWebsocket_Database(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "swarm-global-store-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// create a fresh global store
|
||||
testWebsocket(t, true, "--dir", dir)
|
||||
|
||||
// check if data saved by the previous global store instance
|
||||
testWebsocket(t, false, "--dir", dir)
|
||||
}
|
||||
|
||||
// testWebsocket starts global store binary with WebSocket server
|
||||
// and validates that it can store and retrieve data.
|
||||
// If put is false, no data will be stored, only retrieved,
|
||||
// giving the possibility to check if data is present in the
|
||||
// storage directory.
|
||||
func testWebsocket(t *testing.T, put bool, args ...string) {
|
||||
addr := findFreeTCPAddress(t)
|
||||
testCmd := runGlobalStore(t, append([]string{"ws", "--addr", addr}, args...)...)
|
||||
defer testCmd.Kill()
|
||||
|
||||
client := websocketClient(t, addr)
|
||||
|
||||
store := mockRPC.NewGlobalStore(client)
|
||||
defer store.Close()
|
||||
|
||||
node := store.NewNodeStore(common.HexToAddress("123abc"))
|
||||
|
||||
wantKey := "key"
|
||||
wantValue := "value"
|
||||
|
||||
if put {
|
||||
err := node.Put([]byte(wantKey), []byte(wantValue))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
gotValue, err := node.Get([]byte(wantKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(gotValue) != wantValue {
|
||||
t.Errorf("got value %s for key %s, want %s", string(gotValue), wantKey, wantValue)
|
||||
}
|
||||
}
|
||||
|
||||
// findFreeTCPAddress returns a local address (IP:Port) to which
|
||||
// global store can listen on.
|
||||
func findFreeTCPAddress(t *testing.T) (addr string) {
|
||||
t.Helper()
|
||||
|
||||
listener, err := net.Listen("tcp", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
|
||||
return listener.Addr().String()
|
||||
}
|
||||
|
||||
// websocketClient waits until global store process is started
|
||||
// and returns rpc client.
|
||||
func websocketClient(t *testing.T, addr string) (client *rpc.Client) {
|
||||
t.Helper()
|
||||
|
||||
var err error
|
||||
for i := 0; i < 1000; i++ {
|
||||
client, err = rpc.DialWebsocket(context.Background(), "ws://"+addr, "")
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// waitHTTPEndpoint retries http requests to a provided
|
||||
// address until the connection is established.
|
||||
func waitHTTPEndpoint(t *testing.T, addr string) {
|
||||
t.Helper()
|
||||
|
||||
var err error
|
||||
for i := 0; i < 1000; i++ {
|
||||
_, err = http.Get("http://" + addr)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "0.1"
|
||||
gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
||||
gitDate string
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := newApp().Run(os.Args)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// newApp construct a new instance of Swarm Global Store.
|
||||
// Method Run is called on it in the main function and in tests.
|
||||
func newApp() (app *cli.App) {
|
||||
app = cli.NewApp()
|
||||
app.Name = "global-store"
|
||||
app.Version = version
|
||||
if len(gitCommit) >= 8 {
|
||||
app.Version += "-" + gitCommit[:8]
|
||||
}
|
||||
if gitDate != "" {
|
||||
app.Version += "-" + gitDate
|
||||
}
|
||||
app.Usage = "Swarm Global Store"
|
||||
|
||||
// app flags (for all commands)
|
||||
app.Flags = []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "verbosity",
|
||||
Value: 3,
|
||||
Usage: "Verbosity level.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "explorer-address",
|
||||
Value: "",
|
||||
Usage: "Chunk explorer HTTP listener address.",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "explorer-cors-origin",
|
||||
Value: nil,
|
||||
Usage: "Chunk explorer CORS origin (can be specified multiple times).",
|
||||
},
|
||||
}
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "http",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "Start swarm global store with HTTP server.",
|
||||
Action: startHTTP,
|
||||
// Flags only for "start" command.
|
||||
// Allow app flags to be specified after the
|
||||
// command argument.
|
||||
Flags: append(app.Flags,
|
||||
cli.StringFlag{
|
||||
Name: "dir",
|
||||
Value: "",
|
||||
Usage: "Data directory.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "addr",
|
||||
Value: "0.0.0.0:3033",
|
||||
Usage: "Address to listen for HTTP connections.",
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "websocket",
|
||||
Aliases: []string{"ws"},
|
||||
Usage: "Start swarm global store with WebSocket server.",
|
||||
Action: startWS,
|
||||
// Flags only for "start" command.
|
||||
// Allow app flags to be specified after the
|
||||
// command argument.
|
||||
Flags: append(app.Flags,
|
||||
cli.StringFlag{
|
||||
Name: "dir",
|
||||
Value: "",
|
||||
Usage: "Data directory.",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "addr",
|
||||
Value: "0.0.0.0:3033",
|
||||
Usage: "Address to listen for WebSocket connections.",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "origin",
|
||||
Value: nil,
|
||||
Usage: "WebSocket CORS origin (can be specified multiple times).",
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Command bzzhash computes a swarm tree hash.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/contracts/ens"
|
||||
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var hashCommand = cli.Command{
|
||||
Action: hash,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "hash",
|
||||
Usage: "print the swarm hash of a file or directory",
|
||||
ArgsUsage: "<file>",
|
||||
Description: "Prints the swarm hash of file or directory",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "ens",
|
||||
Usage: "converts a swarm hash to an ens EIP1577 compatible CIDv1 hash",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Action: encodeEipHash,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "contenthash",
|
||||
Usage: "converts a swarm hash to an ens EIP1577 compatible CIDv1 hash",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "",
|
||||
},
|
||||
{
|
||||
Action: ensNodeHash,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "node",
|
||||
Usage: "converts an ens name to an ENS node hash",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
func hash(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) < 1 {
|
||||
utils.Fatalf("Usage: swarm hash <file name>")
|
||||
}
|
||||
f, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
utils.Fatalf("Error opening file " + args[1])
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
stat, _ := f.Stat()
|
||||
fileStore := storage.NewFileStore(&storage.FakeChunkStore{}, storage.NewFileStoreParams(), chunk.NewTags())
|
||||
addr, _, err := fileStore.Store(context.TODO(), f, stat.Size(), false)
|
||||
if err != nil {
|
||||
utils.Fatalf("%v\n", err)
|
||||
} else {
|
||||
fmt.Printf("%v\n", addr)
|
||||
}
|
||||
}
|
||||
func ensNodeHash(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) < 1 {
|
||||
utils.Fatalf("Usage: swarm hash ens node <ens name>")
|
||||
}
|
||||
ensName := args[0]
|
||||
|
||||
hash := ens.EnsNode(ensName)
|
||||
|
||||
stringHex := hex.EncodeToString(hash[:])
|
||||
fmt.Println(stringHex)
|
||||
}
|
||||
func encodeEipHash(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) < 1 {
|
||||
utils.Fatalf("Usage: swarm hash ens <swarm hash>")
|
||||
}
|
||||
swarmHash := args[0]
|
||||
|
||||
hash := common.HexToHash(swarmHash)
|
||||
ensHash, err := ens.EncodeSwarmHash(hash)
|
||||
if err != nil {
|
||||
utils.Fatalf("error converting swarm hash", err)
|
||||
}
|
||||
|
||||
stringHex := hex.EncodeToString(ensHash)
|
||||
fmt.Println(stringHex)
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var listCommand = cli.Command{
|
||||
Action: list,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "ls",
|
||||
Usage: "list files and directories contained in a manifest",
|
||||
ArgsUsage: "<manifest> [<prefix>]",
|
||||
Description: "Lists files and directories contained in a manifest",
|
||||
}
|
||||
|
||||
func list(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
|
||||
if len(args) < 1 {
|
||||
utils.Fatalf("Please supply a manifest reference as the first argument")
|
||||
} else if len(args) > 2 {
|
||||
utils.Fatalf("Too many arguments - usage 'swarm ls manifest [prefix]'")
|
||||
}
|
||||
manifest := args[0]
|
||||
|
||||
var prefix string
|
||||
if len(args) == 2 {
|
||||
prefix = args[1]
|
||||
}
|
||||
|
||||
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
client := swarm.NewClient(bzzapi)
|
||||
list, err := client.List(manifest, prefix, "")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to generate file and directory list: %s", err)
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
|
||||
defer w.Flush()
|
||||
fmt.Fprintln(w, "HASH\tCONTENT TYPE\tPATH")
|
||||
for _, prefix := range list.CommonPrefixes {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\n", "", "DIR", prefix)
|
||||
}
|
||||
for _, entry := range list.Entries {
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\n", entry.Hash, entry.ContentType, entry.Path)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,475 +0,0 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/console"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm"
|
||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||
swarmmetrics "github.com/ethereum/go-ethereum/swarm/metrics"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/mock"
|
||||
mockrpc "github.com/ethereum/go-ethereum/swarm/storage/mock/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm/tracing"
|
||||
sv "github.com/ethereum/go-ethereum/swarm/version"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
const clientIdentifier = "swarm"
|
||||
const helpTemplate = `NAME:
|
||||
{{.HelpName}} - {{.Usage}}
|
||||
|
||||
USAGE:
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
|
||||
|
||||
CATEGORY:
|
||||
{{.Category}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{.Description}}{{end}}{{if .VisibleFlags}}
|
||||
|
||||
OPTIONS:
|
||||
{{range .VisibleFlags}}{{.}}
|
||||
{{end}}{{end}}
|
||||
`
|
||||
|
||||
// Git SHA1 commit hash of the release (set via linker flags)
|
||||
// this variable will be assigned if corresponding parameter is passed with install, but not with test
|
||||
// e.g.: go install -ldflags "-X main.gitCommit=ed1312d01b19e04ef578946226e5d8069d5dfd5a" ./cmd/swarm
|
||||
var gitCommit string
|
||||
|
||||
//declare a few constant error messages, useful for later error check comparisons in test
|
||||
var (
|
||||
SwarmErrNoBZZAccount = "bzzaccount option is required but not set; check your config file, command line or environment variables"
|
||||
SwarmErrSwapSetNoAPI = "SWAP is enabled but --swap-api is not set"
|
||||
)
|
||||
|
||||
// this help command gets added to any subcommand that does not define it explicitly
|
||||
var defaultSubcommandHelp = cli.Command{
|
||||
Action: func(ctx *cli.Context) { cli.ShowCommandHelpAndExit(ctx, "", 1) },
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "help",
|
||||
Usage: "shows this help",
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
var defaultNodeConfig = node.DefaultConfig
|
||||
|
||||
// This init function sets defaults so cmd/swarm can run alongside geth.
|
||||
func init() {
|
||||
sv.GitCommit = gitCommit
|
||||
defaultNodeConfig.Name = clientIdentifier
|
||||
defaultNodeConfig.Version = sv.VersionWithCommit(gitCommit)
|
||||
defaultNodeConfig.P2P.ListenAddr = ":30399"
|
||||
defaultNodeConfig.IPCPath = "bzzd.ipc"
|
||||
// Set flag defaults for --help display.
|
||||
utils.ListenPortFlag.Value = 30399
|
||||
}
|
||||
|
||||
var app = utils.NewApp("", "", "Ethereum Swarm")
|
||||
|
||||
// This init function creates the cli.App.
|
||||
func init() {
|
||||
app.Action = bzzd
|
||||
app.Version = sv.ArchiveVersion(gitCommit)
|
||||
app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Action: version,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "version",
|
||||
Usage: "Print version numbers",
|
||||
Description: "The output of this command is supposed to be machine-readable",
|
||||
},
|
||||
{
|
||||
Action: keys,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "print-keys",
|
||||
Flags: []cli.Flag{SwarmCompressedFlag},
|
||||
Usage: "Print public key information",
|
||||
Description: "The output of this command is supposed to be machine-readable",
|
||||
},
|
||||
// See upload.go
|
||||
upCommand,
|
||||
// See access.go
|
||||
accessCommand,
|
||||
// See feeds.go
|
||||
feedCommand,
|
||||
// See list.go
|
||||
listCommand,
|
||||
// See hash.go
|
||||
hashCommand,
|
||||
// See download.go
|
||||
downloadCommand,
|
||||
// See manifest.go
|
||||
manifestCommand,
|
||||
// See fs.go
|
||||
fsCommand,
|
||||
// See db.go
|
||||
dbCommand,
|
||||
// See config.go
|
||||
DumpConfigCommand,
|
||||
// hashesCommand
|
||||
hashesCommand,
|
||||
}
|
||||
|
||||
// append a hidden help subcommand to all commands that have subcommands
|
||||
// if a help command was already defined above, that one will take precedence.
|
||||
addDefaultHelpSubcommands(app.Commands)
|
||||
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
utils.IdentityFlag,
|
||||
utils.DataDirFlag,
|
||||
utils.BootnodesFlag,
|
||||
utils.KeyStoreDirFlag,
|
||||
utils.ListenPortFlag,
|
||||
utils.DiscoveryV5Flag,
|
||||
utils.NetrestrictFlag,
|
||||
utils.NodeKeyFileFlag,
|
||||
utils.NodeKeyHexFlag,
|
||||
utils.MaxPeersFlag,
|
||||
utils.NATFlag,
|
||||
utils.IPCDisabledFlag,
|
||||
utils.IPCPathFlag,
|
||||
utils.PasswordFileFlag,
|
||||
// bzzd-specific flags
|
||||
CorsStringFlag,
|
||||
EnsAPIFlag,
|
||||
SwarmTomlConfigPathFlag,
|
||||
SwarmSwapEnabledFlag,
|
||||
SwarmSwapAPIFlag,
|
||||
SwarmSyncDisabledFlag,
|
||||
SwarmSyncUpdateDelay,
|
||||
SwarmMaxStreamPeerServersFlag,
|
||||
SwarmLightNodeEnabled,
|
||||
SwarmDeliverySkipCheckFlag,
|
||||
SwarmListenAddrFlag,
|
||||
SwarmPortFlag,
|
||||
SwarmAccountFlag,
|
||||
SwarmNetworkIdFlag,
|
||||
ChequebookAddrFlag,
|
||||
// upload flags
|
||||
SwarmApiFlag,
|
||||
SwarmRecursiveFlag,
|
||||
SwarmWantManifestFlag,
|
||||
SwarmUploadDefaultPath,
|
||||
SwarmUpFromStdinFlag,
|
||||
SwarmUploadMimeType,
|
||||
// bootnode mode
|
||||
SwarmBootnodeModeFlag,
|
||||
// storage flags
|
||||
SwarmStorePath,
|
||||
SwarmStoreCapacity,
|
||||
SwarmStoreCacheCapacity,
|
||||
SwarmGlobalStoreAPIFlag,
|
||||
}
|
||||
rpcFlags := []cli.Flag{
|
||||
utils.WSEnabledFlag,
|
||||
utils.WSListenAddrFlag,
|
||||
utils.WSPortFlag,
|
||||
utils.WSApiFlag,
|
||||
utils.WSAllowedOriginsFlag,
|
||||
}
|
||||
app.Flags = append(app.Flags, rpcFlags...)
|
||||
app.Flags = append(app.Flags, debug.Flags...)
|
||||
app.Flags = append(app.Flags, swarmmetrics.Flags...)
|
||||
app.Flags = append(app.Flags, tracing.Flags...)
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
if err := debug.Setup(ctx, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
swarmmetrics.Setup(ctx)
|
||||
tracing.Setup(ctx)
|
||||
return nil
|
||||
}
|
||||
app.After = func(ctx *cli.Context) error {
|
||||
debug.Exit()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func keys(ctx *cli.Context) error {
|
||||
privateKey := getPrivKey(ctx)
|
||||
pubkey := crypto.FromECDSAPub(&privateKey.PublicKey)
|
||||
pubkeyhex := hex.EncodeToString(pubkey)
|
||||
pubCompressed := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey))
|
||||
bzzkey := crypto.Keccak256Hash(pubkey).Hex()
|
||||
|
||||
if !ctx.Bool(SwarmCompressedFlag.Name) {
|
||||
fmt.Println(fmt.Sprintf("bzzkey=%s", bzzkey[2:]))
|
||||
fmt.Println(fmt.Sprintf("publicKey=%s", pubkeyhex))
|
||||
}
|
||||
fmt.Println(fmt.Sprintf("publicKeyCompressed=%s", pubCompressed))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func version(ctx *cli.Context) error {
|
||||
fmt.Println(strings.Title(clientIdentifier))
|
||||
fmt.Println("Version:", sv.VersionWithMeta)
|
||||
if gitCommit != "" {
|
||||
fmt.Println("Git Commit:", gitCommit)
|
||||
}
|
||||
fmt.Println("Go Version:", runtime.Version())
|
||||
fmt.Println("OS:", runtime.GOOS)
|
||||
return nil
|
||||
}
|
||||
|
||||
func bzzd(ctx *cli.Context) error {
|
||||
//build a valid bzzapi.Config from all available sources:
|
||||
//default config, file config, command line and env vars
|
||||
|
||||
bzzconfig, err := buildConfig(ctx)
|
||||
if err != nil {
|
||||
utils.Fatalf("unable to configure swarm: %v", err)
|
||||
}
|
||||
|
||||
cfg := defaultNodeConfig
|
||||
|
||||
//pss operates on ws
|
||||
cfg.WSModules = append(cfg.WSModules, "pss")
|
||||
|
||||
//geth only supports --datadir via command line
|
||||
//in order to be consistent within swarm, if we pass --datadir via environment variable
|
||||
//or via config file, we get the same directory for geth and swarm
|
||||
if _, err := os.Stat(bzzconfig.Path); err == nil {
|
||||
cfg.DataDir = bzzconfig.Path
|
||||
}
|
||||
|
||||
//optionally set the bootnodes before configuring the node
|
||||
setSwarmBootstrapNodes(ctx, &cfg)
|
||||
//setup the ethereum node
|
||||
utils.SetNodeConfig(ctx, &cfg)
|
||||
|
||||
//disable dynamic dialing from p2p/discovery
|
||||
cfg.P2P.NoDial = true
|
||||
|
||||
stack, err := node.New(&cfg)
|
||||
if err != nil {
|
||||
utils.Fatalf("can't create node: %v", err)
|
||||
}
|
||||
defer stack.Close()
|
||||
|
||||
//a few steps need to be done after the config phase is completed,
|
||||
//due to overriding behavior
|
||||
err = initSwarmNode(bzzconfig, stack, ctx, &cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//register BZZ as node.Service in the ethereum node
|
||||
registerBzzService(bzzconfig, stack)
|
||||
//start the node
|
||||
utils.StartNode(stack)
|
||||
|
||||
go func() {
|
||||
sigc := make(chan os.Signal, 1)
|
||||
signal.Notify(sigc, syscall.SIGTERM)
|
||||
defer signal.Stop(sigc)
|
||||
<-sigc
|
||||
log.Info("Got sigterm, shutting swarm down...")
|
||||
stack.Stop()
|
||||
}()
|
||||
|
||||
// add swarm bootnodes, because swarm doesn't use p2p package's discovery discv5
|
||||
go func() {
|
||||
s := stack.Server()
|
||||
|
||||
for _, n := range cfg.P2P.BootstrapNodes {
|
||||
s.AddPeer(n)
|
||||
}
|
||||
}()
|
||||
|
||||
stack.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func registerBzzService(bzzconfig *bzzapi.Config, stack *node.Node) {
|
||||
//define the swarm service boot function
|
||||
boot := func(_ *node.ServiceContext) (node.Service, error) {
|
||||
var nodeStore *mock.NodeStore
|
||||
if bzzconfig.GlobalStoreAPI != "" {
|
||||
// connect to global store
|
||||
client, err := rpc.Dial(bzzconfig.GlobalStoreAPI)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("global store: %v", err)
|
||||
}
|
||||
globalStore := mockrpc.NewGlobalStore(client)
|
||||
// create a node store for this swarm key on global store
|
||||
nodeStore = globalStore.NewNodeStore(common.HexToAddress(bzzconfig.BzzKey))
|
||||
}
|
||||
return swarm.NewSwarm(bzzconfig, nodeStore)
|
||||
}
|
||||
//register within the ethereum node
|
||||
if err := stack.Register(boot); err != nil {
|
||||
utils.Fatalf("Failed to register the Swarm service: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
|
||||
//an account is mandatory
|
||||
if bzzaccount == "" {
|
||||
utils.Fatalf(SwarmErrNoBZZAccount)
|
||||
}
|
||||
// Try to load the arg as a hex key file.
|
||||
if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
|
||||
log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
|
||||
return key
|
||||
}
|
||||
// Otherwise try getting it from the keystore.
|
||||
am := stack.AccountManager()
|
||||
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||
|
||||
return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
|
||||
}
|
||||
|
||||
// getPrivKey returns the private key of the specified bzzaccount
|
||||
// Used only by client commands, such as `feed`
|
||||
func getPrivKey(ctx *cli.Context) *ecdsa.PrivateKey {
|
||||
// booting up the swarm node just as we do in bzzd action
|
||||
bzzconfig, err := buildConfig(ctx)
|
||||
if err != nil {
|
||||
utils.Fatalf("unable to configure swarm: %v", err)
|
||||
}
|
||||
cfg := defaultNodeConfig
|
||||
if _, err := os.Stat(bzzconfig.Path); err == nil {
|
||||
cfg.DataDir = bzzconfig.Path
|
||||
}
|
||||
utils.SetNodeConfig(ctx, &cfg)
|
||||
stack, err := node.New(&cfg)
|
||||
if err != nil {
|
||||
utils.Fatalf("can't create node: %v", err)
|
||||
}
|
||||
defer stack.Close()
|
||||
|
||||
return getAccount(bzzconfig.BzzAccount, ctx, stack)
|
||||
}
|
||||
|
||||
func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
|
||||
var a accounts.Account
|
||||
var err error
|
||||
if common.IsHexAddress(account) {
|
||||
a, err = ks.Find(accounts.Account{Address: common.HexToAddress(account)})
|
||||
} else if ix, ixerr := strconv.Atoi(account); ixerr == nil && ix > 0 {
|
||||
if accounts := ks.Accounts(); len(accounts) > ix {
|
||||
a = accounts[ix]
|
||||
} else {
|
||||
err = fmt.Errorf("index %d higher than number of accounts %d", ix, len(accounts))
|
||||
}
|
||||
} else {
|
||||
utils.Fatalf("Can't find swarm account key %s", account)
|
||||
}
|
||||
if err != nil {
|
||||
utils.Fatalf("Can't find swarm account key: %v - Is the provided bzzaccount(%s) from the right datadir/Path?", err, account)
|
||||
}
|
||||
keyjson, err := ioutil.ReadFile(a.URL.Path)
|
||||
if err != nil {
|
||||
utils.Fatalf("Can't load swarm account key: %v", err)
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords)
|
||||
key, err := keystore.DecryptKey(keyjson, password)
|
||||
if err == nil {
|
||||
return key.PrivateKey
|
||||
}
|
||||
}
|
||||
utils.Fatalf("Can't decrypt swarm account key")
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPassPhrase retrieves the password associated with bzz account, either by fetching
|
||||
// from a list of pre-loaded passwords, or by requesting it interactively from user.
|
||||
func getPassPhrase(prompt string, i int, passwords []string) string {
|
||||
// non-interactive
|
||||
if len(passwords) > 0 {
|
||||
if i < len(passwords) {
|
||||
return passwords[i]
|
||||
}
|
||||
return passwords[len(passwords)-1]
|
||||
}
|
||||
|
||||
// fallback to interactive mode
|
||||
if prompt != "" {
|
||||
fmt.Println(prompt)
|
||||
}
|
||||
password, err := console.Stdin.PromptPassword("Passphrase: ")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read passphrase: %v", err)
|
||||
}
|
||||
return password
|
||||
}
|
||||
|
||||
// addDefaultHelpSubcommand scans through defined CLI commands and adds
|
||||
// a basic help subcommand to each
|
||||
// if a help command is already defined, it will take precedence over the default.
|
||||
func addDefaultHelpSubcommands(commands []cli.Command) {
|
||||
for i := range commands {
|
||||
cmd := &commands[i]
|
||||
if cmd.Subcommands != nil {
|
||||
cmd.Subcommands = append(cmd.Subcommands, defaultSubcommandHelp)
|
||||
addDefaultHelpSubcommands(cmd.Subcommands)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setSwarmBootstrapNodes(ctx *cli.Context, cfg *node.Config) {
|
||||
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) || ctx.GlobalIsSet(utils.BootnodesV4Flag.Name) {
|
||||
return
|
||||
}
|
||||
|
||||
cfg.P2P.BootstrapNodes = []*enode.Node{}
|
||||
|
||||
for _, url := range SwarmBootnodes {
|
||||
node, err := enode.ParseV4(url)
|
||||
if err != nil {
|
||||
log.Error("Bootstrap URL invalid", "enode", url, "err", err)
|
||||
}
|
||||
cfg.P2P.BootstrapNodes = append(cfg.P2P.BootstrapNodes, node)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,353 +0,0 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Command MANIFEST update
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var manifestCommand = cli.Command{
|
||||
Name: "manifest",
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Usage: "perform operations on swarm manifests",
|
||||
ArgsUsage: "COMMAND",
|
||||
Description: "Updates a MANIFEST by adding/removing/updating the hash of a path.\nCOMMAND could be: add, update, remove",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Action: manifestAdd,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "add",
|
||||
Usage: "add a new path to the manifest",
|
||||
ArgsUsage: "<MANIFEST> <path> <hash>",
|
||||
Description: "Adds a new path to the manifest",
|
||||
},
|
||||
{
|
||||
Action: manifestUpdate,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "update",
|
||||
Usage: "update the hash for an already existing path in the manifest",
|
||||
ArgsUsage: "<MANIFEST> <path> <newhash>",
|
||||
Description: "Update the hash for an already existing path in the manifest",
|
||||
},
|
||||
{
|
||||
Action: manifestRemove,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "remove",
|
||||
Usage: "removes a path from the manifest",
|
||||
ArgsUsage: "<MANIFEST> <path>",
|
||||
Description: "Removes a path from the manifest",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// manifestAdd adds a new entry to the manifest at the given path.
|
||||
// New entry hash, the last argument, must be the hash of a manifest
|
||||
// with only one entry, which meta-data will be added to the original manifest.
|
||||
// On success, this function will print new (updated) manifest's hash.
|
||||
func manifestAdd(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) != 3 {
|
||||
utils.Fatalf("Need exactly three arguments <MHASH> <path> <HASH>")
|
||||
}
|
||||
|
||||
var (
|
||||
mhash = args[0]
|
||||
path = args[1]
|
||||
hash = args[2]
|
||||
)
|
||||
|
||||
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
client := swarm.NewClient(bzzapi)
|
||||
|
||||
m, _, err := client.DownloadManifest(hash)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error downloading manifest to add: %v", err)
|
||||
}
|
||||
l := len(m.Entries)
|
||||
if l == 0 {
|
||||
utils.Fatalf("No entries in manifest %s", hash)
|
||||
} else if l > 1 {
|
||||
utils.Fatalf("Too many entries in manifest %s", hash)
|
||||
}
|
||||
|
||||
newManifest := addEntryToManifest(client, mhash, path, m.Entries[0])
|
||||
fmt.Println(newManifest)
|
||||
}
|
||||
|
||||
// manifestUpdate replaces an existing entry of the manifest at the given path.
|
||||
// New entry hash, the last argument, must be the hash of a manifest
|
||||
// with only one entry, which meta-data will be added to the original manifest.
|
||||
// On success, this function will print hash of the updated manifest.
|
||||
func manifestUpdate(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) != 3 {
|
||||
utils.Fatalf("Need exactly three arguments <MHASH> <path> <HASH>")
|
||||
}
|
||||
|
||||
var (
|
||||
mhash = args[0]
|
||||
path = args[1]
|
||||
hash = args[2]
|
||||
)
|
||||
|
||||
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
client := swarm.NewClient(bzzapi)
|
||||
|
||||
m, _, err := client.DownloadManifest(hash)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error downloading manifest to update: %v", err)
|
||||
}
|
||||
l := len(m.Entries)
|
||||
if l == 0 {
|
||||
utils.Fatalf("No entries in manifest %s", hash)
|
||||
} else if l > 1 {
|
||||
utils.Fatalf("Too many entries in manifest %s", hash)
|
||||
}
|
||||
|
||||
newManifest, _, defaultEntryUpdated := updateEntryInManifest(client, mhash, path, m.Entries[0], true)
|
||||
if defaultEntryUpdated {
|
||||
// Print informational message to stderr
|
||||
// allowing the user to get the new manifest hash from stdout
|
||||
// without the need to parse the complete output.
|
||||
fmt.Fprintln(os.Stderr, "Manifest default entry is updated, too")
|
||||
}
|
||||
fmt.Println(newManifest)
|
||||
}
|
||||
|
||||
// manifestRemove removes an existing entry of the manifest at the given path.
|
||||
// On success, this function will print hash of the manifest which does not
|
||||
// contain the path.
|
||||
func manifestRemove(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) != 2 {
|
||||
utils.Fatalf("Need exactly two arguments <MHASH> <path>")
|
||||
}
|
||||
|
||||
var (
|
||||
mhash = args[0]
|
||||
path = args[1]
|
||||
)
|
||||
|
||||
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
client := swarm.NewClient(bzzapi)
|
||||
|
||||
newManifest := removeEntryFromManifest(client, mhash, path)
|
||||
fmt.Println(newManifest)
|
||||
}
|
||||
|
||||
func addEntryToManifest(client *swarm.Client, mhash, path string, entry api.ManifestEntry) string {
|
||||
var longestPathEntry = api.ManifestEntry{}
|
||||
|
||||
mroot, isEncrypted, err := client.DownloadManifest(mhash)
|
||||
if err != nil {
|
||||
utils.Fatalf("Manifest download failed: %v", err)
|
||||
}
|
||||
|
||||
// See if we path is in this Manifest or do we have to dig deeper
|
||||
for _, e := range mroot.Entries {
|
||||
if path == e.Path {
|
||||
utils.Fatalf("Path %s already present, not adding anything", path)
|
||||
} else {
|
||||
if e.ContentType == api.ManifestType {
|
||||
prfxlen := strings.HasPrefix(path, e.Path)
|
||||
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
||||
longestPathEntry = e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if longestPathEntry.Path != "" {
|
||||
// Load the child Manifest add the entry there
|
||||
newPath := path[len(longestPathEntry.Path):]
|
||||
newHash := addEntryToManifest(client, longestPathEntry.Hash, newPath, entry)
|
||||
|
||||
// Replace the hash for parent Manifests
|
||||
newMRoot := &api.Manifest{}
|
||||
for _, e := range mroot.Entries {
|
||||
if longestPathEntry.Path == e.Path {
|
||||
e.Hash = newHash
|
||||
}
|
||||
newMRoot.Entries = append(newMRoot.Entries, e)
|
||||
}
|
||||
mroot = newMRoot
|
||||
} else {
|
||||
// Add the entry in the leaf Manifest
|
||||
entry.Path = path
|
||||
mroot.Entries = append(mroot.Entries, entry)
|
||||
}
|
||||
|
||||
newManifestHash, err := client.UploadManifest(mroot, isEncrypted)
|
||||
if err != nil {
|
||||
utils.Fatalf("Manifest upload failed: %v", err)
|
||||
}
|
||||
return newManifestHash
|
||||
}
|
||||
|
||||
// updateEntryInManifest updates an existing entry o path with a new one in the manifest with provided mhash
|
||||
// finding the path recursively through all nested manifests. Argument isRoot is used for default
|
||||
// entry update detection. If the updated entry has the same hash as the default entry, then the
|
||||
// default entry in root manifest will be updated too.
|
||||
// Returned values are the new manifest hash, hash of the entry that was replaced by the new entry and
|
||||
// a a bool that is true if default entry is updated.
|
||||
func updateEntryInManifest(client *swarm.Client, mhash, path string, entry api.ManifestEntry, isRoot bool) (newManifestHash, oldHash string, defaultEntryUpdated bool) {
|
||||
var (
|
||||
newEntry = api.ManifestEntry{}
|
||||
longestPathEntry = api.ManifestEntry{}
|
||||
)
|
||||
|
||||
mroot, isEncrypted, err := client.DownloadManifest(mhash)
|
||||
if err != nil {
|
||||
utils.Fatalf("Manifest download failed: %v", err)
|
||||
}
|
||||
|
||||
// See if we path is in this Manifest or do we have to dig deeper
|
||||
for _, e := range mroot.Entries {
|
||||
if path == e.Path {
|
||||
newEntry = e
|
||||
// keep the reference of the hash of the entry that should be replaced
|
||||
// for default entry detection
|
||||
oldHash = e.Hash
|
||||
} else {
|
||||
if e.ContentType == api.ManifestType {
|
||||
prfxlen := strings.HasPrefix(path, e.Path)
|
||||
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
||||
longestPathEntry = e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if longestPathEntry.Path == "" && newEntry.Path == "" {
|
||||
utils.Fatalf("Path %s not present in the Manifest, not setting anything", path)
|
||||
}
|
||||
|
||||
if longestPathEntry.Path != "" {
|
||||
// Load the child Manifest add the entry there
|
||||
newPath := path[len(longestPathEntry.Path):]
|
||||
var newHash string
|
||||
newHash, oldHash, _ = updateEntryInManifest(client, longestPathEntry.Hash, newPath, entry, false)
|
||||
|
||||
// Replace the hash for parent Manifests
|
||||
newMRoot := &api.Manifest{}
|
||||
for _, e := range mroot.Entries {
|
||||
if longestPathEntry.Path == e.Path {
|
||||
e.Hash = newHash
|
||||
}
|
||||
newMRoot.Entries = append(newMRoot.Entries, e)
|
||||
|
||||
}
|
||||
mroot = newMRoot
|
||||
}
|
||||
|
||||
// update the manifest if the new entry is found and
|
||||
// check if default entry should be updated
|
||||
if newEntry.Path != "" || isRoot {
|
||||
// Replace the hash for leaf Manifest
|
||||
newMRoot := &api.Manifest{}
|
||||
for _, e := range mroot.Entries {
|
||||
if newEntry.Path == e.Path {
|
||||
entry.Path = e.Path
|
||||
newMRoot.Entries = append(newMRoot.Entries, entry)
|
||||
} else if isRoot && e.Path == "" && e.Hash == oldHash {
|
||||
entry.Path = e.Path
|
||||
newMRoot.Entries = append(newMRoot.Entries, entry)
|
||||
defaultEntryUpdated = true
|
||||
} else {
|
||||
newMRoot.Entries = append(newMRoot.Entries, e)
|
||||
}
|
||||
}
|
||||
mroot = newMRoot
|
||||
}
|
||||
|
||||
newManifestHash, err = client.UploadManifest(mroot, isEncrypted)
|
||||
if err != nil {
|
||||
utils.Fatalf("Manifest upload failed: %v", err)
|
||||
}
|
||||
return newManifestHash, oldHash, defaultEntryUpdated
|
||||
}
|
||||
|
||||
func removeEntryFromManifest(client *swarm.Client, mhash, path string) string {
|
||||
var (
|
||||
entryToRemove = api.ManifestEntry{}
|
||||
longestPathEntry = api.ManifestEntry{}
|
||||
)
|
||||
|
||||
mroot, isEncrypted, err := client.DownloadManifest(mhash)
|
||||
if err != nil {
|
||||
utils.Fatalf("Manifest download failed: %v", err)
|
||||
}
|
||||
|
||||
// See if we path is in this Manifest or do we have to dig deeper
|
||||
for _, entry := range mroot.Entries {
|
||||
if path == entry.Path {
|
||||
entryToRemove = entry
|
||||
} else {
|
||||
if entry.ContentType == api.ManifestType {
|
||||
prfxlen := strings.HasPrefix(path, entry.Path)
|
||||
if prfxlen && len(path) > len(longestPathEntry.Path) {
|
||||
longestPathEntry = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if longestPathEntry.Path == "" && entryToRemove.Path == "" {
|
||||
utils.Fatalf("Path %s not present in the Manifest, not removing anything", path)
|
||||
}
|
||||
|
||||
if longestPathEntry.Path != "" {
|
||||
// Load the child Manifest remove the entry there
|
||||
newPath := path[len(longestPathEntry.Path):]
|
||||
newHash := removeEntryFromManifest(client, longestPathEntry.Hash, newPath)
|
||||
|
||||
// Replace the hash for parent Manifests
|
||||
newMRoot := &api.Manifest{}
|
||||
for _, entry := range mroot.Entries {
|
||||
if longestPathEntry.Path == entry.Path {
|
||||
entry.Hash = newHash
|
||||
}
|
||||
newMRoot.Entries = append(newMRoot.Entries, entry)
|
||||
}
|
||||
mroot = newMRoot
|
||||
}
|
||||
|
||||
if entryToRemove.Path != "" {
|
||||
// remove the entry in this Manifest
|
||||
newMRoot := &api.Manifest{}
|
||||
for _, entry := range mroot.Entries {
|
||||
if entryToRemove.Path != entry.Path {
|
||||
newMRoot.Entries = append(newMRoot.Entries, entry)
|
||||
}
|
||||
}
|
||||
mroot = newMRoot
|
||||
}
|
||||
|
||||
newManifestHash, err := client.UploadManifest(mroot, isEncrypted)
|
||||
if err != nil {
|
||||
utils.Fatalf("Manifest upload failed: %v", err)
|
||||
}
|
||||
return newManifestHash
|
||||
}
|
||||
|
|
@ -1,597 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||
)
|
||||
|
||||
// TestManifestChange tests manifest add, update and remove
|
||||
// cli commands without encryption.
|
||||
func TestManifestChange(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
testManifestChange(t, false)
|
||||
}
|
||||
|
||||
// TestManifestChange tests manifest add, update and remove
|
||||
// cli commands with encryption enabled.
|
||||
func TestManifestChangeEncrypted(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
testManifestChange(t, true)
|
||||
}
|
||||
|
||||
// testManifestChange performs cli commands:
|
||||
// - manifest add
|
||||
// - manifest update
|
||||
// - manifest remove
|
||||
// on a manifest, testing the functionality of this
|
||||
// comands on paths that are in root manifest or a nested one.
|
||||
// Argument encrypt controls whether to use encryption or not.
|
||||
func testManifestChange(t *testing.T, encrypt bool) {
|
||||
t.Parallel()
|
||||
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
tmp, err := ioutil.TempDir("", "swarm-manifest-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
origDir := filepath.Join(tmp, "orig")
|
||||
if err := os.Mkdir(origDir, 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
indexDataFilename := filepath.Join(origDir, "index.html")
|
||||
err = ioutil.WriteFile(indexDataFilename, []byte("<h1>Test</h1>"), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Files paths robots.txt and robots.html share the same prefix "robots."
|
||||
// which will result a manifest with a nested manifest under path "robots.".
|
||||
// This will allow testing manifest changes on both root and nested manifest.
|
||||
err = ioutil.WriteFile(filepath.Join(origDir, "robots.txt"), []byte("Disallow: /"), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(origDir, "robots.html"), []byte("<strong>No Robots Allowed</strong>"), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(origDir, "mutants.txt"), []byte("Frank\nMarcus"), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"--recursive",
|
||||
"--defaultpath",
|
||||
indexDataFilename,
|
||||
"up",
|
||||
origDir,
|
||||
}
|
||||
if encrypt {
|
||||
args = append(args, "--encrypt")
|
||||
}
|
||||
|
||||
origManifestHash := runSwarmExpectHash(t, args...)
|
||||
|
||||
checkHashLength(t, origManifestHash, encrypt)
|
||||
|
||||
client := swarm.NewClient(srv.URL)
|
||||
|
||||
// upload a new file and use its manifest to add it the original manifest.
|
||||
t.Run("add", func(t *testing.T) {
|
||||
humansData := []byte("Ann\nBob")
|
||||
humansDataFilename := filepath.Join(tmp, "humans.txt")
|
||||
err = ioutil.WriteFile(humansDataFilename, humansData, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
humansManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"up",
|
||||
humansDataFilename,
|
||||
)
|
||||
|
||||
newManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"manifest",
|
||||
"add",
|
||||
origManifestHash,
|
||||
"humans.txt",
|
||||
humansManifestHash,
|
||||
)
|
||||
|
||||
checkHashLength(t, newManifestHash, encrypt)
|
||||
|
||||
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
|
||||
|
||||
var found bool
|
||||
for _, e := range newManifest.Entries {
|
||||
if e.Path == "humans.txt" {
|
||||
found = true
|
||||
if e.Size != int64(len(humansData)) {
|
||||
t.Errorf("expected humans.txt size %v, got %v", len(humansData), e.Size)
|
||||
}
|
||||
if e.ModTime.IsZero() {
|
||||
t.Errorf("got zero mod time for humans.txt")
|
||||
}
|
||||
ct := "text/plain; charset=utf-8"
|
||||
if e.ContentType != ct {
|
||||
t.Errorf("expected content type %q, got %q", ct, e.ContentType)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("no humans.txt in new manifest")
|
||||
}
|
||||
|
||||
checkFile(t, client, newManifestHash, "humans.txt", humansData)
|
||||
})
|
||||
|
||||
// upload a new file and use its manifest to add it the original manifest,
|
||||
// but ensure that the file will be in the nested manifest of the original one.
|
||||
t.Run("add nested", func(t *testing.T) {
|
||||
robotsData := []byte(`{"disallow": "/"}`)
|
||||
robotsDataFilename := filepath.Join(tmp, "robots.json")
|
||||
err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
robotsManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"up",
|
||||
robotsDataFilename,
|
||||
)
|
||||
|
||||
newManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"manifest",
|
||||
"add",
|
||||
origManifestHash,
|
||||
"robots.json",
|
||||
robotsManifestHash,
|
||||
)
|
||||
|
||||
checkHashLength(t, newManifestHash, encrypt)
|
||||
|
||||
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
|
||||
|
||||
var found bool
|
||||
loop:
|
||||
for _, e := range newManifest.Entries {
|
||||
if e.Path == "robots." {
|
||||
nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
|
||||
for _, e := range nestedManifest.Entries {
|
||||
if e.Path == "json" {
|
||||
found = true
|
||||
if e.Size != int64(len(robotsData)) {
|
||||
t.Errorf("expected robots.json size %v, got %v", len(robotsData), e.Size)
|
||||
}
|
||||
if e.ModTime.IsZero() {
|
||||
t.Errorf("got zero mod time for robots.json")
|
||||
}
|
||||
ct := "application/json"
|
||||
if e.ContentType != ct {
|
||||
t.Errorf("expected content type %q, got %q", ct, e.ContentType)
|
||||
}
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("no robots.json in new manifest")
|
||||
}
|
||||
|
||||
checkFile(t, client, newManifestHash, "robots.json", robotsData)
|
||||
})
|
||||
|
||||
// upload a new file and use its manifest to change the file it the original manifest.
|
||||
t.Run("update", func(t *testing.T) {
|
||||
indexData := []byte("<h1>Ethereum Swarm</h1>")
|
||||
indexDataFilename := filepath.Join(tmp, "index.html")
|
||||
err = ioutil.WriteFile(indexDataFilename, indexData, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
indexManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"up",
|
||||
indexDataFilename,
|
||||
)
|
||||
|
||||
newManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"manifest",
|
||||
"update",
|
||||
origManifestHash,
|
||||
"index.html",
|
||||
indexManifestHash,
|
||||
)
|
||||
|
||||
checkHashLength(t, newManifestHash, encrypt)
|
||||
|
||||
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
|
||||
|
||||
var found bool
|
||||
for _, e := range newManifest.Entries {
|
||||
if e.Path == "index.html" {
|
||||
found = true
|
||||
if e.Size != int64(len(indexData)) {
|
||||
t.Errorf("expected index.html size %v, got %v", len(indexData), e.Size)
|
||||
}
|
||||
if e.ModTime.IsZero() {
|
||||
t.Errorf("got zero mod time for index.html")
|
||||
}
|
||||
ct := "text/html; charset=utf-8"
|
||||
if e.ContentType != ct {
|
||||
t.Errorf("expected content type %q, got %q", ct, e.ContentType)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("no index.html in new manifest")
|
||||
}
|
||||
|
||||
checkFile(t, client, newManifestHash, "index.html", indexData)
|
||||
|
||||
// check default entry change
|
||||
checkFile(t, client, newManifestHash, "", indexData)
|
||||
})
|
||||
|
||||
// upload a new file and use its manifest to change the file it the original manifest,
|
||||
// but ensure that the file is in the nested manifest of the original one.
|
||||
t.Run("update nested", func(t *testing.T) {
|
||||
robotsData := []byte(`<string>Only humans allowed!!!</strong>`)
|
||||
robotsDataFilename := filepath.Join(tmp, "robots.html")
|
||||
err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
humansManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"up",
|
||||
robotsDataFilename,
|
||||
)
|
||||
|
||||
newManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"manifest",
|
||||
"update",
|
||||
origManifestHash,
|
||||
"robots.html",
|
||||
humansManifestHash,
|
||||
)
|
||||
|
||||
checkHashLength(t, newManifestHash, encrypt)
|
||||
|
||||
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
|
||||
|
||||
var found bool
|
||||
loop:
|
||||
for _, e := range newManifest.Entries {
|
||||
if e.Path == "robots." {
|
||||
nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
|
||||
for _, e := range nestedManifest.Entries {
|
||||
if e.Path == "html" {
|
||||
found = true
|
||||
if e.Size != int64(len(robotsData)) {
|
||||
t.Errorf("expected robots.html size %v, got %v", len(robotsData), e.Size)
|
||||
}
|
||||
if e.ModTime.IsZero() {
|
||||
t.Errorf("got zero mod time for robots.html")
|
||||
}
|
||||
ct := "text/html; charset=utf-8"
|
||||
if e.ContentType != ct {
|
||||
t.Errorf("expected content type %q, got %q", ct, e.ContentType)
|
||||
}
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("no robots.html in new manifest")
|
||||
}
|
||||
|
||||
checkFile(t, client, newManifestHash, "robots.html", robotsData)
|
||||
})
|
||||
|
||||
// remove a file from the manifest.
|
||||
t.Run("remove", func(t *testing.T) {
|
||||
newManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"manifest",
|
||||
"remove",
|
||||
origManifestHash,
|
||||
"mutants.txt",
|
||||
)
|
||||
|
||||
checkHashLength(t, newManifestHash, encrypt)
|
||||
|
||||
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
|
||||
|
||||
var found bool
|
||||
for _, e := range newManifest.Entries {
|
||||
if e.Path == "mutants.txt" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
t.Fatal("mutants.txt is not removed")
|
||||
}
|
||||
})
|
||||
|
||||
// remove a file from the manifest, but ensure that the file is in
|
||||
// the nested manifest of the original one.
|
||||
t.Run("remove nested", func(t *testing.T) {
|
||||
newManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"manifest",
|
||||
"remove",
|
||||
origManifestHash,
|
||||
"robots.html",
|
||||
)
|
||||
|
||||
checkHashLength(t, newManifestHash, encrypt)
|
||||
|
||||
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
|
||||
|
||||
var found bool
|
||||
loop:
|
||||
for _, e := range newManifest.Entries {
|
||||
if e.Path == "robots." {
|
||||
nestedManifest := downloadManifest(t, client, e.Hash, encrypt)
|
||||
for _, e := range nestedManifest.Entries {
|
||||
if e.Path == "html" {
|
||||
found = true
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if found {
|
||||
t.Fatal("robots.html in not removed")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestNestedDefaultEntryUpdate tests if the default entry is updated
|
||||
// if the file in nested manifest used for it is also updated.
|
||||
func TestNestedDefaultEntryUpdate(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
testNestedDefaultEntryUpdate(t, false)
|
||||
}
|
||||
|
||||
// TestNestedDefaultEntryUpdateEncrypted tests if the default entry
|
||||
// of encrypted upload is updated if the file in nested manifest
|
||||
// used for it is also updated.
|
||||
func TestNestedDefaultEntryUpdateEncrypted(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
testNestedDefaultEntryUpdate(t, true)
|
||||
}
|
||||
|
||||
func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
|
||||
t.Parallel()
|
||||
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||
defer srv.Close()
|
||||
|
||||
tmp, err := ioutil.TempDir("", "swarm-manifest-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
origDir := filepath.Join(tmp, "orig")
|
||||
if err := os.Mkdir(origDir, 0777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
indexData := []byte("<h1>Test</h1>")
|
||||
indexDataFilename := filepath.Join(origDir, "index.html")
|
||||
err = ioutil.WriteFile(indexDataFilename, indexData, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Add another file with common prefix as the default entry to test updates of
|
||||
// default entry with nested manifests.
|
||||
err = ioutil.WriteFile(filepath.Join(origDir, "index.txt"), []byte("Test"), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"--recursive",
|
||||
"--defaultpath",
|
||||
indexDataFilename,
|
||||
"up",
|
||||
origDir,
|
||||
}
|
||||
if encrypt {
|
||||
args = append(args, "--encrypt")
|
||||
}
|
||||
|
||||
origManifestHash := runSwarmExpectHash(t, args...)
|
||||
|
||||
checkHashLength(t, origManifestHash, encrypt)
|
||||
|
||||
client := swarm.NewClient(srv.URL)
|
||||
|
||||
newIndexData := []byte("<h1>Ethereum Swarm</h1>")
|
||||
newIndexDataFilename := filepath.Join(tmp, "index.html")
|
||||
err = ioutil.WriteFile(newIndexDataFilename, newIndexData, 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newIndexManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"up",
|
||||
newIndexDataFilename,
|
||||
)
|
||||
|
||||
newManifestHash := runSwarmExpectHash(t,
|
||||
"--bzzapi",
|
||||
srv.URL,
|
||||
"manifest",
|
||||
"update",
|
||||
origManifestHash,
|
||||
"index.html",
|
||||
newIndexManifestHash,
|
||||
)
|
||||
|
||||
checkHashLength(t, newManifestHash, encrypt)
|
||||
|
||||
newManifest := downloadManifest(t, client, newManifestHash, encrypt)
|
||||
|
||||
var found bool
|
||||
for _, e := range newManifest.Entries {
|
||||
if e.Path == "index." {
|
||||
found = true
|
||||
newManifest = downloadManifest(t, client, e.Hash, encrypt)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("no index. path in new manifest")
|
||||
}
|
||||
|
||||
found = false
|
||||
for _, e := range newManifest.Entries {
|
||||
if e.Path == "html" {
|
||||
found = true
|
||||
if e.Size != int64(len(newIndexData)) {
|
||||
t.Errorf("expected index.html size %v, got %v", len(newIndexData), e.Size)
|
||||
}
|
||||
if e.ModTime.IsZero() {
|
||||
t.Errorf("got zero mod time for index.html")
|
||||
}
|
||||
ct := "text/html; charset=utf-8"
|
||||
if e.ContentType != ct {
|
||||
t.Errorf("expected content type %q, got %q", ct, e.ContentType)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("no html in new manifest")
|
||||
}
|
||||
|
||||
checkFile(t, client, newManifestHash, "index.html", newIndexData)
|
||||
|
||||
// check default entry change
|
||||
checkFile(t, client, newManifestHash, "", newIndexData)
|
||||
}
|
||||
|
||||
func runSwarmExpectHash(t *testing.T, args ...string) (hash string) {
|
||||
t.Helper()
|
||||
hashRegexp := `[a-f\d]{64,128}`
|
||||
up := runSwarm(t, args...)
|
||||
_, matches := up.ExpectRegexp(hashRegexp)
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) < 1 {
|
||||
t.Fatal("no matches found")
|
||||
}
|
||||
return matches[0]
|
||||
}
|
||||
|
||||
func checkHashLength(t *testing.T, hash string, encrypted bool) {
|
||||
t.Helper()
|
||||
l := len(hash)
|
||||
if encrypted && l != 128 {
|
||||
t.Errorf("expected hash length 128, got %v", l)
|
||||
}
|
||||
if !encrypted && l != 64 {
|
||||
t.Errorf("expected hash length 64, got %v", l)
|
||||
}
|
||||
}
|
||||
|
||||
func downloadManifest(t *testing.T, client *swarm.Client, hash string, encrypted bool) (manifest *api.Manifest) {
|
||||
t.Helper()
|
||||
m, isEncrypted, err := client.DownloadManifest(hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if encrypted != isEncrypted {
|
||||
t.Error("new manifest encryption flag is not correct")
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func checkFile(t *testing.T, client *swarm.Client, hash, path string, expected []byte) {
|
||||
t.Helper()
|
||||
f, err := client.Download(hash, path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(got, expected) {
|
||||
t.Errorf("expected file content %q, got %q", expected, got)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
package main
|
||||
|
||||
// Standard "mime" package rely on system-settings, see mime.osInitMime
|
||||
// Swarm will run on many OS/Platform/Docker and must behave similar
|
||||
// This command generates code to add common mime types based on mime.types file
|
||||
//
|
||||
// mime.types file provided by mailcap, which follow https://www.iana.org/assignments/media-types/media-types.xhtml
|
||||
//
|
||||
// Get last version of mime.types file by:
|
||||
// docker run --rm -v $(pwd):/tmp alpine:edge /bin/sh -c "apk add -U mailcap; mv /etc/mime.types /tmp"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"flag"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"log"
|
||||
)
|
||||
|
||||
var (
|
||||
typesFlag = flag.String("types", "", "Input mime.types file")
|
||||
packageFlag = flag.String("package", "", "Golang package in output file")
|
||||
outFlag = flag.String("out", "", "Output file name for the generated mime types")
|
||||
)
|
||||
|
||||
type mime struct {
|
||||
Name string
|
||||
Exts []string
|
||||
}
|
||||
|
||||
type templateParams struct {
|
||||
PackageName string
|
||||
Mimes []mime
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Parse and ensure all needed inputs are specified
|
||||
flag.Parse()
|
||||
if *typesFlag == "" {
|
||||
log.Fatalf("--types is required")
|
||||
}
|
||||
if *packageFlag == "" {
|
||||
log.Fatalf("--types is required")
|
||||
}
|
||||
if *outFlag == "" {
|
||||
log.Fatalf("--out is required")
|
||||
}
|
||||
|
||||
params := templateParams{
|
||||
PackageName: *packageFlag,
|
||||
}
|
||||
|
||||
types, err := ioutil.ReadFile(*typesFlag)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(bytes.NewReader(types))
|
||||
for scanner.Scan() {
|
||||
txt := scanner.Text()
|
||||
if strings.HasPrefix(txt, "#") || len(txt) == 0 {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(txt)
|
||||
if len(parts) == 1 {
|
||||
continue
|
||||
}
|
||||
params.Mimes = append(params.Mimes, mime{parts[0], parts[1:]})
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
result := bytes.NewBuffer([]byte{})
|
||||
|
||||
if err := template.Must(template.New("_").Parse(tpl)).Execute(result, params); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(*outFlag, result.Bytes(), 0600); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var tpl = `// Code generated by github.com/ethereum/go-ethereum/cmd/swarm/mimegen. DO NOT EDIT.
|
||||
|
||||
package {{ .PackageName }}
|
||||
|
||||
import "mime"
|
||||
func init() {
|
||||
var mimeTypes = map[string]string{
|
||||
{{- range .Mimes -}}
|
||||
{{ $name := .Name -}}
|
||||
{{- range .Exts }}
|
||||
".{{ . }}": "{{ $name | html }}",
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
}
|
||||
for ext, name := range mimeTypes {
|
||||
if err := mime.AddExtensionType(ext, name); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,502 +0,0 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||
)
|
||||
|
||||
var loglevel = flag.Int("loglevel", 3, "verbosity of logs")
|
||||
|
||||
func init() {
|
||||
// Run the app if we've been exec'd as "swarm-test" in runSwarm.
|
||||
reexec.Register("swarm-test", func() {
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
})
|
||||
}
|
||||
|
||||
const clusterSize = 3
|
||||
|
||||
func serverFunc(api *api.API) swarmhttp.TestServer {
|
||||
return swarmhttp.NewServer(api, "")
|
||||
}
|
||||
func TestMain(m *testing.M) {
|
||||
// check if we have been reexec'd
|
||||
if reexec.Init() {
|
||||
return
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func runSwarm(t *testing.T, args ...string) *cmdtest.TestCmd {
|
||||
tt := cmdtest.NewTestCmd(t, nil)
|
||||
|
||||
found := false
|
||||
for _, v := range args {
|
||||
if v == "--bootnodes" {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
args = append([]string{"--bootnodes", ""}, args...)
|
||||
}
|
||||
|
||||
// Boot "swarm". This actually runs the test binary but the TestMain
|
||||
// function will prevent any tests from running.
|
||||
tt.Run("swarm-test", args...)
|
||||
|
||||
return tt
|
||||
}
|
||||
|
||||
type testCluster struct {
|
||||
Nodes []*testNode
|
||||
TmpDir string
|
||||
}
|
||||
|
||||
// newTestCluster starts a test swarm cluster of the given size.
|
||||
//
|
||||
// A temporary directory is created and each node gets a data directory inside
|
||||
// it.
|
||||
//
|
||||
// Each node listens on 127.0.0.1 with random ports for both the HTTP and p2p
|
||||
// ports (assigned by first listening on 127.0.0.1:0 and then passing the ports
|
||||
// as flags).
|
||||
//
|
||||
// When starting more than one node, they are connected together using the
|
||||
// admin SetPeer RPC method.
|
||||
|
||||
func newTestCluster(t *testing.T, size int) *testCluster {
|
||||
cluster := &testCluster{}
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
cluster.Shutdown()
|
||||
}
|
||||
}()
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "swarm-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cluster.TmpDir = tmpdir
|
||||
|
||||
// start the nodes
|
||||
cluster.StartNewNodes(t, size)
|
||||
|
||||
if size == 1 {
|
||||
return cluster
|
||||
}
|
||||
|
||||
// connect the nodes together
|
||||
for _, node := range cluster.Nodes {
|
||||
if err := node.Client.Call(nil, "admin_addPeer", cluster.Nodes[0].Enode); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// wait until all nodes have the correct number of peers
|
||||
outer:
|
||||
for _, node := range cluster.Nodes {
|
||||
var peers []*p2p.PeerInfo
|
||||
for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(50 * time.Millisecond) {
|
||||
if err := node.Client.Call(&peers, "admin_peers"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(peers) == len(cluster.Nodes)-1 {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
t.Fatalf("%s only has %d / %d peers", node.Name, len(peers), len(cluster.Nodes)-1)
|
||||
}
|
||||
|
||||
return cluster
|
||||
}
|
||||
|
||||
func (c *testCluster) Shutdown() {
|
||||
c.Stop()
|
||||
c.Cleanup()
|
||||
}
|
||||
|
||||
func (c *testCluster) Stop() {
|
||||
for _, node := range c.Nodes {
|
||||
node.Shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *testCluster) StartNewNodes(t *testing.T, size int) {
|
||||
c.Nodes = make([]*testNode, 0, size)
|
||||
|
||||
errors := make(chan error, size)
|
||||
nodes := make(chan *testNode, size)
|
||||
for i := 0; i < size; i++ {
|
||||
go func(nodeIndex int) {
|
||||
dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", nodeIndex))
|
||||
if err := os.Mkdir(dir, 0700); err != nil {
|
||||
errors <- err
|
||||
return
|
||||
}
|
||||
|
||||
node := newTestNode(t, dir)
|
||||
node.Name = fmt.Sprintf("swarm%02d", nodeIndex)
|
||||
nodes <- node
|
||||
}(i)
|
||||
}
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
select {
|
||||
case node := <-nodes:
|
||||
c.Nodes = append(c.Nodes, node)
|
||||
case err := <-errors:
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
if t.Failed() {
|
||||
c.Shutdown()
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *testCluster) StartExistingNodes(t *testing.T, size int, bzzaccount string) {
|
||||
c.Nodes = make([]*testNode, 0, size)
|
||||
for i := 0; i < size; i++ {
|
||||
dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", i))
|
||||
node := existingTestNode(t, dir, bzzaccount)
|
||||
node.Name = fmt.Sprintf("swarm%02d", i)
|
||||
|
||||
c.Nodes = append(c.Nodes, node)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *testCluster) Cleanup() {
|
||||
os.RemoveAll(c.TmpDir)
|
||||
}
|
||||
|
||||
type testNode struct {
|
||||
Name string
|
||||
Addr string
|
||||
URL string
|
||||
Enode string
|
||||
Dir string
|
||||
IpcPath string
|
||||
PrivateKey *ecdsa.PrivateKey
|
||||
Client *rpc.Client
|
||||
Cmd *cmdtest.TestCmd
|
||||
}
|
||||
|
||||
const testPassphrase = "swarm-test-passphrase"
|
||||
|
||||
func getTestAccount(t *testing.T, dir string) (conf *node.Config, account accounts.Account) {
|
||||
// create key
|
||||
conf = &node.Config{
|
||||
DataDir: dir,
|
||||
IPCPath: "bzzd.ipc",
|
||||
NoUSB: true,
|
||||
}
|
||||
n, err := node.New(conf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
account, err = n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// use a unique IPCPath when running tests on Windows
|
||||
if runtime.GOOS == "windows" {
|
||||
conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String())
|
||||
}
|
||||
|
||||
return conf, account
|
||||
}
|
||||
|
||||
func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
|
||||
conf, _ := getTestAccount(t, dir)
|
||||
node := &testNode{Dir: dir}
|
||||
|
||||
// use a unique IPCPath when running tests on Windows
|
||||
if runtime.GOOS == "windows" {
|
||||
conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", bzzaccount)
|
||||
}
|
||||
|
||||
// assign ports
|
||||
ports, err := getAvailableTCPPorts(2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p2pPort := ports[0]
|
||||
httpPort := ports[1]
|
||||
|
||||
// start the node
|
||||
node.Cmd = runSwarm(t,
|
||||
"--bootnodes", "",
|
||||
"--port", p2pPort,
|
||||
"--nat", "extip:127.0.0.1",
|
||||
"--datadir", dir,
|
||||
"--ipcpath", conf.IPCPath,
|
||||
"--ens-api", "",
|
||||
"--bzzaccount", bzzaccount,
|
||||
"--bzznetworkid", "321",
|
||||
"--bzzport", httpPort,
|
||||
"--verbosity", fmt.Sprint(*loglevel),
|
||||
)
|
||||
node.Cmd.InputLine(testPassphrase)
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
node.Shutdown()
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// ensure that all ports have active listeners
|
||||
// so that the next node will not get the same
|
||||
// when calling getAvailableTCPPorts
|
||||
err = waitTCPPorts(ctx, ports...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// wait for the node to start
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if node.Client == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// load info
|
||||
var info swarm.Info
|
||||
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
|
||||
node.URL = "http://" + node.Addr
|
||||
|
||||
var nodeInfo p2p.NodeInfo
|
||||
if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
node.Enode = nodeInfo.Enode
|
||||
node.IpcPath = conf.IPCPath
|
||||
return node
|
||||
}
|
||||
|
||||
func newTestNode(t *testing.T, dir string) *testNode {
|
||||
|
||||
conf, account := getTestAccount(t, dir)
|
||||
ks := keystore.NewKeyStore(path.Join(dir, "keystore"), 1<<18, 1)
|
||||
|
||||
pk := decryptStoreAccount(ks, account.Address.Hex(), []string{testPassphrase})
|
||||
|
||||
node := &testNode{Dir: dir, PrivateKey: pk}
|
||||
|
||||
// assign ports
|
||||
ports, err := getAvailableTCPPorts(2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p2pPort := ports[0]
|
||||
httpPort := ports[1]
|
||||
|
||||
// start the node
|
||||
node.Cmd = runSwarm(t,
|
||||
"--bootnodes", "",
|
||||
"--port", p2pPort,
|
||||
"--nat", "extip:127.0.0.1",
|
||||
"--datadir", dir,
|
||||
"--ipcpath", conf.IPCPath,
|
||||
"--ens-api", "",
|
||||
"--bzzaccount", account.Address.String(),
|
||||
"--bzznetworkid", "321",
|
||||
"--bzzport", httpPort,
|
||||
"--verbosity", fmt.Sprint(*loglevel),
|
||||
)
|
||||
node.Cmd.InputLine(testPassphrase)
|
||||
defer func() {
|
||||
if t.Failed() {
|
||||
node.Shutdown()
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// ensure that all ports have active listeners
|
||||
// so that the next node will not get the same
|
||||
// when calling getAvailableTCPPorts
|
||||
err = waitTCPPorts(ctx, ports...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// wait for the node to start
|
||||
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
|
||||
node.Client, err = rpc.Dial(conf.IPCEndpoint())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if node.Client == nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// load info
|
||||
var info swarm.Info
|
||||
if err := node.Client.Call(&info, "bzz_info"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
node.Addr = net.JoinHostPort("127.0.0.1", info.Port)
|
||||
node.URL = "http://" + node.Addr
|
||||
|
||||
var nodeInfo p2p.NodeInfo
|
||||
if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
node.Enode = nodeInfo.Enode
|
||||
node.IpcPath = conf.IPCPath
|
||||
return node
|
||||
}
|
||||
|
||||
func (n *testNode) Shutdown() {
|
||||
if n.Cmd != nil {
|
||||
n.Cmd.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
// getAvailableTCPPorts returns a set of ports that
|
||||
// nothing is listening on at the time.
|
||||
//
|
||||
// Function assignTCPPort cannot be called in sequence
|
||||
// and guardantee that the same port will be returned in
|
||||
// different calls as the listener is closed within the function,
|
||||
// not after all listeners are started and selected unique
|
||||
// available ports.
|
||||
func getAvailableTCPPorts(count int) (ports []string, err error) {
|
||||
for i := 0; i < count; i++ {
|
||||
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// defer close in the loop to be sure the same port will not
|
||||
// be selected in the next iteration
|
||||
defer l.Close()
|
||||
|
||||
_, port, err := net.SplitHostPort(l.Addr().String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ports = append(ports, port)
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
// waitTCPPorts blocks until tcp connections can be
|
||||
// established on all provided ports. It runs all
|
||||
// ports dialers in parallel, and returns the first
|
||||
// encountered error.
|
||||
// See waitTCPPort also.
|
||||
func waitTCPPorts(ctx context.Context, ports ...string) error {
|
||||
var err error
|
||||
// mu locks err variable that is assigned in
|
||||
// other goroutines
|
||||
var mu sync.Mutex
|
||||
|
||||
// cancel is canceling all goroutines
|
||||
// when the firs error is returned
|
||||
// to prevent unnecessary waiting
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for _, port := range ports {
|
||||
wg.Add(1)
|
||||
go func(port string) {
|
||||
defer wg.Done()
|
||||
|
||||
e := waitTCPPort(ctx, port)
|
||||
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if e != nil && err == nil {
|
||||
err = e
|
||||
cancel()
|
||||
}
|
||||
}(port)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// waitTCPPort blocks until tcp connection can be established
|
||||
// ona provided port. It has a 3 minute timeout as maximum,
|
||||
// to prevent long waiting, but it can be shortened with
|
||||
// a provided context instance. Dialer has a 10 second timeout
|
||||
// in every iteration, and connection refused error will be
|
||||
// retried in 100 milliseconds periods.
|
||||
func waitTCPPort(ctx context.Context, port string) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 3*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
for {
|
||||
c, err := (&net.Dialer{Timeout: 10 * time.Second}).DialContext(ctx, "tcp", "127.0.0.1:"+port)
|
||||
if err != nil {
|
||||
if operr, ok := err.(*net.OpError); ok {
|
||||
if syserr, ok := operr.Err.(*os.SyscallError); ok && syserr.Err == syscall.ECONNREFUSED {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return c.Close()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,291 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
"github.com/pborman/uuid"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
const (
|
||||
feedRandomDataLength = 8
|
||||
)
|
||||
|
||||
func feedUploadAndSyncCmd(ctx *cli.Context) error {
|
||||
errc := make(chan error)
|
||||
|
||||
go func() {
|
||||
errc <- feedUploadAndSync(ctx)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-errc:
|
||||
if err != nil {
|
||||
metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1)
|
||||
}
|
||||
return err
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
metrics.GetOrRegisterCounter(fmt.Sprintf("%s.timeout", commandName), nil).Inc(1)
|
||||
|
||||
return fmt.Errorf("timeout after %v sec", timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func feedUploadAndSync(c *cli.Context) error {
|
||||
log.Info("generating and uploading feeds to " + httpEndpoint(hosts[0]) + " and syncing")
|
||||
|
||||
// create a random private key to sign updates with and derive the address
|
||||
pkFile, err := ioutil.TempFile("", "swarm-feed-smoke-test")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pkFile.Close()
|
||||
defer os.Remove(pkFile.Name())
|
||||
|
||||
privkeyHex := "0000000000000000000000000000000000000000000000000000000000001976"
|
||||
privKey, err := crypto.HexToECDSA(privkeyHex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user := crypto.PubkeyToAddress(privKey.PublicKey)
|
||||
userHex := hexutil.Encode(user.Bytes())
|
||||
|
||||
// save the private key to a file
|
||||
_, err = io.WriteString(pkFile, privkeyHex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// keep hex strings for topic and subtopic
|
||||
var topicHex string
|
||||
var subTopicHex string
|
||||
|
||||
// and create combination hex topics for bzz-feed retrieval
|
||||
// xor'ed with topic (zero-value topic if no topic)
|
||||
var subTopicOnlyHex string
|
||||
var mergedSubTopicHex string
|
||||
|
||||
// generate random topic and subtopic and put a hex on them
|
||||
topicBytes, err := generateRandomData(feed.TopicLength)
|
||||
topicHex = hexutil.Encode(topicBytes)
|
||||
subTopicBytes, err := generateRandomData(8)
|
||||
subTopicHex = hexutil.Encode(subTopicBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mergedSubTopic, err := feed.NewTopic(subTopicHex, topicBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mergedSubTopicHex = hexutil.Encode(mergedSubTopic[:])
|
||||
subTopicOnlyBytes, err := feed.NewTopic(subTopicHex, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subTopicOnlyHex = hexutil.Encode(subTopicOnlyBytes[:])
|
||||
|
||||
// create feed manifest, topic only
|
||||
var out bytes.Buffer
|
||||
cmd := exec.Command("swarm", "--bzzapi", httpEndpoint(hosts[0]), "feed", "create", "--topic", topicHex, "--user", userHex)
|
||||
cmd.Stdout = &out
|
||||
log.Debug("create feed manifest topic cmd", "cmd", cmd)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
manifestWithTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
|
||||
if len(manifestWithTopic) != 64 {
|
||||
return fmt.Errorf("unknown feed create manifest hash format (topic): (%d) %s", len(out.String()), manifestWithTopic)
|
||||
}
|
||||
log.Debug("create topic feed", "manifest", manifestWithTopic)
|
||||
out.Reset()
|
||||
|
||||
// create feed manifest, subtopic only
|
||||
cmd = exec.Command("swarm", "--bzzapi", httpEndpoint(hosts[0]), "feed", "create", "--name", subTopicHex, "--user", userHex)
|
||||
cmd.Stdout = &out
|
||||
log.Debug("create feed manifest subtopic cmd", "cmd", cmd)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
manifestWithSubTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
|
||||
if len(manifestWithSubTopic) != 64 {
|
||||
return fmt.Errorf("unknown feed create manifest hash format (subtopic): (%d) %s", len(out.String()), manifestWithSubTopic)
|
||||
}
|
||||
log.Debug("create subtopic feed", "manifest", manifestWithTopic)
|
||||
out.Reset()
|
||||
|
||||
// create feed manifest, merged topic
|
||||
cmd = exec.Command("swarm", "--bzzapi", httpEndpoint(hosts[0]), "feed", "create", "--topic", topicHex, "--name", subTopicHex, "--user", userHex)
|
||||
cmd.Stdout = &out
|
||||
log.Debug("create feed manifest mergetopic cmd", "cmd", cmd)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
manifestWithMergedTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
|
||||
if len(manifestWithMergedTopic) != 64 {
|
||||
return fmt.Errorf("unknown feed create manifest hash format (mergedtopic): (%d) %s", len(out.String()), manifestWithMergedTopic)
|
||||
}
|
||||
log.Debug("create mergedtopic feed", "manifest", manifestWithMergedTopic)
|
||||
out.Reset()
|
||||
|
||||
// create test data
|
||||
data, err := generateRandomData(feedRandomDataLength)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h := md5.New()
|
||||
h.Write(data)
|
||||
dataHash := h.Sum(nil)
|
||||
dataHex := hexutil.Encode(data)
|
||||
|
||||
// update with topic
|
||||
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, dataHex)
|
||||
cmd.Stdout = &out
|
||||
log.Debug("update feed manifest topic cmd", "cmd", cmd)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("feed update topic", "out", out)
|
||||
out.Reset()
|
||||
|
||||
// update with subtopic
|
||||
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--name", subTopicHex, dataHex)
|
||||
cmd.Stdout = &out
|
||||
log.Debug("update feed manifest subtopic cmd", "cmd", cmd)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("feed update subtopic", "out", out)
|
||||
out.Reset()
|
||||
|
||||
// update with merged topic
|
||||
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, "--name", subTopicHex, dataHex)
|
||||
cmd.Stdout = &out
|
||||
log.Debug("update feed manifest merged topic cmd", "cmd", cmd)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("feed update mergedtopic", "out", out)
|
||||
out.Reset()
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// retrieve the data
|
||||
wg := sync.WaitGroup{}
|
||||
for _, host := range hosts {
|
||||
// raw retrieve, topic only
|
||||
for _, hex := range []string{topicHex, subTopicOnlyHex, mergedSubTopicHex} {
|
||||
wg.Add(1)
|
||||
ruid := uuid.New()[:8]
|
||||
go func(hex string, endpoint string, ruid string) {
|
||||
for {
|
||||
err := fetchFeed(hex, userHex, httpEndpoint(host), dataHash, ruid)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}(hex, httpEndpoint(host), ruid)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
log.Info("all endpoints synced random data successfully")
|
||||
|
||||
// upload test file
|
||||
log.Info("feed uploading to "+httpEndpoint(hosts[0])+" and syncing", "seed", seed)
|
||||
|
||||
randomBytes := testutil.RandomBytes(seed, filesize*1000)
|
||||
|
||||
hash, err := upload(randomBytes, httpEndpoint(hosts[0]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hashBytes, err := hexutil.Decode("0x" + hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
multihashHex := hexutil.Encode(hashBytes)
|
||||
fileHash := h.Sum(nil)
|
||||
|
||||
log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fileHash))
|
||||
|
||||
// update file with topic
|
||||
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, multihashHex)
|
||||
cmd.Stdout = &out
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("feed update topic", "out", out)
|
||||
out.Reset()
|
||||
|
||||
// update file with subtopic
|
||||
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--name", subTopicHex, multihashHex)
|
||||
cmd.Stdout = &out
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("feed update subtopic", "out", out)
|
||||
out.Reset()
|
||||
|
||||
// update file with merged topic
|
||||
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, "--name", subTopicHex, multihashHex)
|
||||
cmd.Stdout = &out
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debug("feed update mergedtopic", "out", out)
|
||||
out.Reset()
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
for _, host := range hosts {
|
||||
|
||||
// manifest retrieve, topic only
|
||||
for _, url := range []string{manifestWithTopic, manifestWithSubTopic, manifestWithMergedTopic} {
|
||||
wg.Add(1)
|
||||
ruid := uuid.New()[:8]
|
||||
go func(url string, endpoint string, ruid string) {
|
||||
for {
|
||||
err := fetch(url, endpoint, fileHash, ruid)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
return
|
||||
}
|
||||
}(url, httpEndpoint(host), ruid)
|
||||
}
|
||||
|
||||
}
|
||||
wg.Wait()
|
||||
log.Info("all endpoints synced random file successfully")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
gethmetrics "github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/metrics/influxdb"
|
||||
swarmmetrics "github.com/ethereum/go-ethereum/swarm/metrics"
|
||||
"github.com/ethereum/go-ethereum/swarm/tracing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
||||
)
|
||||
|
||||
var (
|
||||
allhosts string
|
||||
hosts []string
|
||||
filesize int
|
||||
syncDelay bool
|
||||
inputSeed int
|
||||
httpPort int
|
||||
wsPort int
|
||||
verbosity int
|
||||
timeout int
|
||||
single bool
|
||||
onlyUpload bool
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "smoke-test"
|
||||
app.Usage = ""
|
||||
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "hosts",
|
||||
Value: "",
|
||||
Usage: "comma-separated list of swarm hosts",
|
||||
Destination: &allhosts,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "http-port",
|
||||
Value: 80,
|
||||
Usage: "http port",
|
||||
Destination: &httpPort,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "ws-port",
|
||||
Value: 8546,
|
||||
Usage: "ws port",
|
||||
Destination: &wsPort,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "seed",
|
||||
Value: 0,
|
||||
Usage: "input seed in case we need deterministic upload",
|
||||
Destination: &inputSeed,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "filesize",
|
||||
Value: 1024,
|
||||
Usage: "file size for generated random file in KB",
|
||||
Destination: &filesize,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "sync-delay",
|
||||
Usage: "wait for content to be synced",
|
||||
Destination: &syncDelay,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "verbosity",
|
||||
Value: 1,
|
||||
Usage: "verbosity",
|
||||
Destination: &verbosity,
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "timeout",
|
||||
Value: 180,
|
||||
Usage: "timeout in seconds after which kill the process",
|
||||
Destination: &timeout,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "single",
|
||||
Usage: "whether to fetch content from a single node or from all nodes",
|
||||
Destination: &single,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "only-upload",
|
||||
Usage: "whether to only upload content to a single node without fetching",
|
||||
Destination: &onlyUpload,
|
||||
},
|
||||
}
|
||||
|
||||
app.Flags = append(app.Flags, []cli.Flag{
|
||||
utils.MetricsEnabledFlag,
|
||||
swarmmetrics.MetricsInfluxDBEndpointFlag,
|
||||
swarmmetrics.MetricsInfluxDBDatabaseFlag,
|
||||
swarmmetrics.MetricsInfluxDBUsernameFlag,
|
||||
swarmmetrics.MetricsInfluxDBPasswordFlag,
|
||||
swarmmetrics.MetricsInfluxDBTagsFlag,
|
||||
}...)
|
||||
|
||||
app.Flags = append(app.Flags, tracing.Flags...)
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "upload_and_sync",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "upload and sync",
|
||||
Action: wrapCliCommand("upload-and-sync", uploadAndSyncCmd),
|
||||
},
|
||||
{
|
||||
Name: "feed_sync",
|
||||
Aliases: []string{"f"},
|
||||
Usage: "feed update generate, upload and sync",
|
||||
Action: wrapCliCommand("feed-and-sync", feedUploadAndSyncCmd),
|
||||
},
|
||||
{
|
||||
Name: "upload_speed",
|
||||
Aliases: []string{"u"},
|
||||
Usage: "measure upload speed",
|
||||
Action: wrapCliCommand("upload-speed", uploadSpeedCmd),
|
||||
},
|
||||
{
|
||||
Name: "sliding_window",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "measure network aggregate capacity",
|
||||
Action: wrapCliCommand("sliding-window", slidingWindowCmd),
|
||||
},
|
||||
}
|
||||
|
||||
sort.Sort(cli.FlagsByName(app.Flags))
|
||||
sort.Sort(cli.CommandsByName(app.Commands))
|
||||
|
||||
app.Before = func(ctx *cli.Context) error {
|
||||
tracing.Setup(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
app.After = func(ctx *cli.Context) error {
|
||||
return emitMetrics(ctx)
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func emitMetrics(ctx *cli.Context) error {
|
||||
if gethmetrics.Enabled {
|
||||
var (
|
||||
endpoint = ctx.GlobalString(swarmmetrics.MetricsInfluxDBEndpointFlag.Name)
|
||||
database = ctx.GlobalString(swarmmetrics.MetricsInfluxDBDatabaseFlag.Name)
|
||||
username = ctx.GlobalString(swarmmetrics.MetricsInfluxDBUsernameFlag.Name)
|
||||
password = ctx.GlobalString(swarmmetrics.MetricsInfluxDBPasswordFlag.Name)
|
||||
tags = ctx.GlobalString(swarmmetrics.MetricsInfluxDBTagsFlag.Name)
|
||||
)
|
||||
|
||||
tagsMap := utils.SplitTagsFlag(tags)
|
||||
tagsMap["version"] = gitCommit
|
||||
tagsMap["filesize"] = fmt.Sprintf("%v", filesize)
|
||||
|
||||
return influxdb.InfluxDBWithTagsOnce(gethmetrics.DefaultRegistry, endpoint, database, username, password, "swarm-smoke.", tagsMap)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
type uploadResult struct {
|
||||
hash string
|
||||
digest []byte
|
||||
}
|
||||
|
||||
func slidingWindowCmd(ctx *cli.Context) error {
|
||||
errc := make(chan error)
|
||||
|
||||
go func() {
|
||||
errc <- slidingWindow(ctx)
|
||||
}()
|
||||
|
||||
err := <-errc
|
||||
if err != nil {
|
||||
metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func slidingWindow(ctx *cli.Context) error {
|
||||
var hashes []uploadResult //swarm hashes of the uploads
|
||||
nodes := len(hosts)
|
||||
log.Info("sliding window test started", "nodes", nodes, "filesize(kb)", filesize, "timeout", timeout)
|
||||
uploadedBytes := 0
|
||||
networkDepth := 0
|
||||
errored := false
|
||||
|
||||
outer:
|
||||
for {
|
||||
seed = int(time.Now().UTC().UnixNano())
|
||||
log.Info("uploading to "+httpEndpoint(hosts[0])+" and syncing", "seed", seed)
|
||||
|
||||
t1 := time.Now()
|
||||
|
||||
randomBytes := testutil.RandomBytes(seed, filesize*1000)
|
||||
|
||||
hash, err := upload(randomBytes, httpEndpoint(hosts[0]))
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
metrics.GetOrRegisterResettingTimer("sliding-window.upload-time", nil).UpdateSince(t1)
|
||||
metrics.GetOrRegisterGauge("sliding-window.upload-depth", nil).Update(int64(len(hashes)))
|
||||
|
||||
fhash, err := digest(bytes.NewReader(randomBytes))
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fhash), "wait for sync", syncDelay)
|
||||
hashes = append(hashes, uploadResult{hash: hash, digest: fhash})
|
||||
|
||||
if syncDelay {
|
||||
waitToSync()
|
||||
}
|
||||
|
||||
uploadedBytes += filesize * 1000
|
||||
q := make(chan struct{}, 1)
|
||||
d := make(chan struct{})
|
||||
defer close(q)
|
||||
defer close(d)
|
||||
for i, v := range hashes {
|
||||
timeoutC := time.After(time.Duration(timeout) * time.Second)
|
||||
errored = false
|
||||
|
||||
task:
|
||||
for {
|
||||
select {
|
||||
case q <- struct{}{}:
|
||||
go func() {
|
||||
var start time.Time
|
||||
done := false
|
||||
for !done {
|
||||
log.Info("trying to retrieve hash", "hash", v.hash)
|
||||
idx := 1 + rand.Intn(len(hosts)-1)
|
||||
ruid := uuid.New()[:8]
|
||||
start = time.Now()
|
||||
// fetch hangs when swarm dies out, so we have to jump through a bit more hoops to actually
|
||||
// catch the timeout, but also allow this retry logic
|
||||
err := fetch(v.hash, httpEndpoint(hosts[idx]), v.digest, ruid)
|
||||
if err != nil {
|
||||
log.Error("error fetching hash", "err", err)
|
||||
continue
|
||||
}
|
||||
done = true
|
||||
}
|
||||
metrics.GetOrRegisterResettingTimer("sliding-window.single.fetch-time", nil).UpdateSince(start)
|
||||
d <- struct{}{}
|
||||
}()
|
||||
case <-d:
|
||||
<-q
|
||||
break task
|
||||
case <-timeoutC:
|
||||
errored = true
|
||||
log.Error("error retrieving hash. timeout", "hash idx", i)
|
||||
metrics.GetOrRegisterCounter("sliding-window.single.error", nil).Inc(1)
|
||||
break outer
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
networkDepth = i
|
||||
metrics.GetOrRegisterGauge("sliding-window.network-depth", nil).Update(int64(networkDepth))
|
||||
log.Info("sliding window test successfully fetched file", "currentDepth", networkDepth)
|
||||
// this test might take a long time to finish - but we'd like to see metrics while they accumulate and not just when
|
||||
// the test finishes. therefore emit the metrics on each iteration
|
||||
emitMetrics(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("sliding window test finished", "errored?", errored, "networkDepth", networkDepth, "networkDepth(kb)", networkDepth*filesize)
|
||||
log.Info("stats", "uploadedFiles", len(hashes), "uploadedKb", uploadedBytes/1000, "filesizeKb", filesize)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,376 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/ethereum/go-ethereum/swarm/chunk"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
func uploadAndSyncCmd(ctx *cli.Context) error {
|
||||
// use input seed if it has been set
|
||||
if inputSeed != 0 {
|
||||
seed = inputSeed
|
||||
}
|
||||
|
||||
randomBytes := testutil.RandomBytes(seed, filesize*1000)
|
||||
|
||||
errc := make(chan error)
|
||||
|
||||
go func() {
|
||||
errc <- uploadAndSync(ctx, randomBytes)
|
||||
}()
|
||||
|
||||
var err error
|
||||
select {
|
||||
case err = <-errc:
|
||||
if err != nil {
|
||||
metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1)
|
||||
}
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
metrics.GetOrRegisterCounter(fmt.Sprintf("%s.timeout", commandName), nil).Inc(1)
|
||||
|
||||
err = fmt.Errorf("timeout after %v sec", timeout)
|
||||
}
|
||||
|
||||
// trigger debug functionality on randomBytes
|
||||
e := trackChunks(randomBytes[:], true)
|
||||
if e != nil {
|
||||
log.Error(e.Error())
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func trackChunks(testData []byte, submitMetrics bool) error {
|
||||
addrs, err := getAllRefs(testData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, ref := range addrs {
|
||||
log.Debug(fmt.Sprintf("ref %d", i), "ref", ref)
|
||||
}
|
||||
|
||||
var globalYes, globalNo int
|
||||
var globalMu sync.Mutex
|
||||
var hasErr bool
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(hosts))
|
||||
|
||||
var mu sync.Mutex // mutex protecting the allHostsChunks and bzzAddrs maps
|
||||
allHostChunks := map[string]string{} // host->bitvector of presence for chunks
|
||||
bzzAddrs := map[string]string{} // host->bzzAddr
|
||||
|
||||
for _, host := range hosts {
|
||||
host := host
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
httpHost := fmt.Sprintf("ws://%s:%d", host, 8546)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
rpcClient, err := rpc.DialContext(ctx, httpHost)
|
||||
if rpcClient != nil {
|
||||
defer rpcClient.Close()
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("error dialing host", "err", err, "host", httpHost)
|
||||
hasErr = true
|
||||
return
|
||||
}
|
||||
|
||||
hostChunks, err := getChunksBitVectorFromHost(rpcClient, addrs)
|
||||
if err != nil {
|
||||
log.Error("error getting chunks bit vector from host", "err", err, "host", httpHost)
|
||||
hasErr = true
|
||||
return
|
||||
}
|
||||
|
||||
bzzAddr, err := getBzzAddrFromHost(rpcClient)
|
||||
if err != nil {
|
||||
log.Error("error getting bzz addrs from host", "err", err, "host", httpHost)
|
||||
hasErr = true
|
||||
return
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
allHostChunks[host] = hostChunks
|
||||
bzzAddrs[host] = bzzAddr
|
||||
mu.Unlock()
|
||||
|
||||
yes, no := 0, 0
|
||||
for _, val := range hostChunks {
|
||||
if val == '1' {
|
||||
yes++
|
||||
} else {
|
||||
no++
|
||||
}
|
||||
}
|
||||
|
||||
if no == 0 {
|
||||
log.Info("host reported to have all chunks", "host", host)
|
||||
}
|
||||
|
||||
log.Debug("chunks", "chunks", hostChunks, "yes", yes, "no", no, "host", host)
|
||||
|
||||
if submitMetrics {
|
||||
globalMu.Lock()
|
||||
globalYes += yes
|
||||
globalNo += no
|
||||
globalMu.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
checkChunksVsMostProxHosts(addrs, allHostChunks, bzzAddrs)
|
||||
|
||||
if !hasErr && submitMetrics {
|
||||
// remove the chunks stored on the uploader node
|
||||
globalYes -= len(addrs)
|
||||
|
||||
metrics.GetOrRegisterCounter("deployment.chunks.yes", nil).Inc(int64(globalYes))
|
||||
metrics.GetOrRegisterCounter("deployment.chunks.no", nil).Inc(int64(globalNo))
|
||||
metrics.GetOrRegisterCounter("deployment.chunks.refs", nil).Inc(int64(len(addrs)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getChunksBitVectorFromHost returns a bit vector of presence for a given slice of chunks from a given host
|
||||
func getChunksBitVectorFromHost(client *rpc.Client, addrs []storage.Address) (string, error) {
|
||||
var hostChunks string
|
||||
|
||||
err := client.Call(&hostChunks, "bzz_has", addrs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return hostChunks, nil
|
||||
}
|
||||
|
||||
// getBzzAddrFromHost returns the bzzAddr for a given host
|
||||
func getBzzAddrFromHost(client *rpc.Client) (string, error) {
|
||||
var hive string
|
||||
|
||||
err := client.Call(&hive, "bzz_hive")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// we make an ugly assumption about the output format of the hive.String() method
|
||||
// ideally we should replace this with an API call that returns the bzz addr for a given host,
|
||||
// but this also works for now (provided we don't change the hive.String() method, which we haven't in some time
|
||||
ss := strings.Split(strings.Split(hive, "\n")[3], " ")
|
||||
return ss[len(ss)-1], nil
|
||||
}
|
||||
|
||||
// checkChunksVsMostProxHosts is checking:
|
||||
// 1. whether a chunk has been found at less than 2 hosts. Considering our NN size, this should not happen.
|
||||
// 2. if a chunk is not found at its closest node. This should also not happen.
|
||||
// Together with the --only-upload flag, we could run this smoke test and make sure that our syncing
|
||||
// functionality is correct (without even trying to retrieve the content).
|
||||
//
|
||||
// addrs - a slice with all uploaded chunk refs
|
||||
// allHostChunks - host->bit vector, showing what chunks are present on what hosts
|
||||
// bzzAddrs - host->bzz address, used when determining the most proximate host for a given chunk
|
||||
func checkChunksVsMostProxHosts(addrs []storage.Address, allHostChunks map[string]string, bzzAddrs map[string]string) {
|
||||
for k, v := range bzzAddrs {
|
||||
log.Trace("bzzAddr", "bzz", v, "host", k)
|
||||
}
|
||||
|
||||
for i := range addrs {
|
||||
var foundAt int
|
||||
maxProx := -1
|
||||
var maxProxHost string
|
||||
for host := range allHostChunks {
|
||||
if allHostChunks[host][i] == '1' {
|
||||
foundAt++
|
||||
}
|
||||
|
||||
ba, err := hex.DecodeString(bzzAddrs[host])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// calculate the host closest to any chunk
|
||||
prox := chunk.Proximity(addrs[i], ba)
|
||||
if prox > maxProx {
|
||||
maxProx = prox
|
||||
maxProxHost = host
|
||||
}
|
||||
}
|
||||
|
||||
if allHostChunks[maxProxHost][i] == '0' {
|
||||
log.Error("chunk not found at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
|
||||
} else {
|
||||
log.Trace("chunk present at max prox host", "ref", addrs[i], "host", maxProxHost, "bzzAddr", bzzAddrs[maxProxHost])
|
||||
}
|
||||
|
||||
// if chunk found at less than 2 hosts
|
||||
if foundAt < 2 {
|
||||
log.Error("chunk found at less than two hosts", "foundAt", foundAt, "ref", addrs[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getAllRefs(testData []byte) (storage.AddressCollection, error) {
|
||||
datadir, err := ioutil.TempDir("", "chunk-debug")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(datadir)
|
||||
fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32), chunk.NewTags())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(testData)
|
||||
return fileStore.GetAllReferences(context.Background(), reader, false)
|
||||
}
|
||||
|
||||
func uploadAndSync(c *cli.Context, randomBytes []byte) error {
|
||||
log.Info("uploading to "+httpEndpoint(hosts[0])+" and syncing", "seed", seed)
|
||||
|
||||
t1 := time.Now()
|
||||
hash, err := upload(randomBytes, httpEndpoint(hosts[0]))
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
t2 := time.Since(t1)
|
||||
metrics.GetOrRegisterResettingTimer("upload-and-sync.upload-time", nil).Update(t2)
|
||||
|
||||
fhash, err := digest(bytes.NewReader(randomBytes))
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("uploaded successfully", "hash", hash, "took", t2, "digest", fmt.Sprintf("%x", fhash))
|
||||
|
||||
// wait to sync and log chunks before fetch attempt, only if syncDelay is set to true
|
||||
if syncDelay {
|
||||
waitToSync()
|
||||
|
||||
log.Debug("chunks before fetch attempt", "hash", hash)
|
||||
|
||||
err = trackChunks(randomBytes, false)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if onlyUpload {
|
||||
log.Debug("only-upload is true, stoppping test", "hash", hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
randIndex := 1 + rand.Intn(len(hosts)-1)
|
||||
|
||||
for {
|
||||
start := time.Now()
|
||||
err := fetch(hash, httpEndpoint(hosts[randIndex]), fhash, "")
|
||||
if err != nil {
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
ended := time.Since(start)
|
||||
|
||||
metrics.GetOrRegisterResettingTimer("upload-and-sync.single.fetch-time", nil).Update(ended)
|
||||
log.Info("fetch successful", "took", ended, "endpoint", httpEndpoint(hosts[randIndex]))
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSyncing(wsHost string) (bool, error) {
|
||||
rpcClient, err := rpc.Dial(wsHost)
|
||||
if rpcClient != nil {
|
||||
defer rpcClient.Close()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error("error dialing host", "err", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
var isSyncing bool
|
||||
err = rpcClient.Call(&isSyncing, "bzz_isSyncing")
|
||||
if err != nil {
|
||||
log.Error("error calling host for isSyncing", "err", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
log.Debug("isSyncing result", "host", wsHost, "isSyncing", isSyncing)
|
||||
|
||||
return isSyncing, nil
|
||||
}
|
||||
|
||||
func waitToSync() {
|
||||
t1 := time.Now()
|
||||
|
||||
ns := uint64(1)
|
||||
|
||||
for ns > 0 {
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
notSynced := uint64(0)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(hosts))
|
||||
for i := 0; i < len(hosts); i++ {
|
||||
i := i
|
||||
go func(idx int) {
|
||||
stillSyncing, err := isSyncing(wsEndpoint(hosts[idx]))
|
||||
|
||||
if stillSyncing || err != nil {
|
||||
atomic.AddUint64(¬Synced, 1)
|
||||
}
|
||||
wg.Done()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
ns = atomic.LoadUint64(¬Synced)
|
||||
}
|
||||
|
||||
t2 := time.Since(t1)
|
||||
metrics.GetOrRegisterResettingTimer("upload-and-sync.single.wait-for-sync.deployment", nil).Update(t2)
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
func uploadSpeedCmd(ctx *cli.Context) error {
|
||||
log.Info("uploading to "+hosts[0], "seed", seed)
|
||||
randomBytes := testutil.RandomBytes(seed, filesize*1000)
|
||||
|
||||
errc := make(chan error)
|
||||
|
||||
go func() {
|
||||
errc <- uploadSpeed(ctx, randomBytes)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-errc:
|
||||
if err != nil {
|
||||
metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1)
|
||||
}
|
||||
return err
|
||||
case <-time.After(time.Duration(timeout) * time.Second):
|
||||
metrics.GetOrRegisterCounter(fmt.Sprintf("%s.timeout", commandName), nil).Inc(1)
|
||||
|
||||
// trigger debug functionality on randomBytes
|
||||
|
||||
return fmt.Errorf("timeout after %v sec", timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func uploadSpeed(c *cli.Context, data []byte) error {
|
||||
t1 := time.Now()
|
||||
hash, err := upload(data, hosts[0])
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
metrics.GetOrRegisterCounter("upload-speed.upload-time", nil).Inc(int64(time.Since(t1)))
|
||||
|
||||
fhash, err := digest(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fhash))
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
"github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
commandName = ""
|
||||
seed = int(time.Now().UTC().UnixNano())
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(int64(seed))
|
||||
}
|
||||
|
||||
func httpEndpoint(host string) string {
|
||||
return fmt.Sprintf("http://%s:%d", host, httpPort)
|
||||
}
|
||||
|
||||
func wsEndpoint(host string) string {
|
||||
return fmt.Sprintf("ws://%s:%d", host, wsPort)
|
||||
}
|
||||
|
||||
func wrapCliCommand(name string, command func(*cli.Context) error) func(*cli.Context) error {
|
||||
return func(ctx *cli.Context) error {
|
||||
log.PrintOrigins(true)
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(verbosity), log.StreamHandler(os.Stdout, log.TerminalFormat(false))))
|
||||
|
||||
commandName = name
|
||||
|
||||
hosts = strings.Split(allhosts, ",")
|
||||
|
||||
defer func(now time.Time) {
|
||||
totalTime := time.Since(now)
|
||||
log.Info("total time", "time", totalTime, "kb", filesize)
|
||||
metrics.GetOrRegisterResettingTimer(name+".total-time", nil).Update(totalTime)
|
||||
}(time.Now())
|
||||
|
||||
log.Info("smoke test starting", "task", name, "timeout", timeout)
|
||||
metrics.GetOrRegisterCounter(name, nil).Inc(1)
|
||||
|
||||
return command(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchFeed(topic string, user string, endpoint string, original []byte, ruid string) error {
|
||||
ctx, sp := spancontext.StartSpan(context.Background(), "feed-and-sync.fetch")
|
||||
defer sp.Finish()
|
||||
|
||||
log.Trace("sleeping", "ruid", ruid)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
log.Trace("http get request (feed)", "ruid", ruid, "api", endpoint, "topic", topic, "user", user)
|
||||
|
||||
var tn time.Time
|
||||
reqUri := endpoint + "/bzz-feed:/?topic=" + topic + "&user=" + user
|
||||
req, _ := http.NewRequest("GET", reqUri, nil)
|
||||
|
||||
opentracing.GlobalTracer().Inject(
|
||||
sp.Context(),
|
||||
opentracing.HTTPHeaders,
|
||||
opentracing.HTTPHeadersCarrier(req.Header))
|
||||
|
||||
trace := client.GetClientTrace("feed-and-sync - http get", "feed-and-sync", ruid, &tn)
|
||||
|
||||
req = req.WithContext(httptrace.WithClientTrace(ctx, trace))
|
||||
transport := http.DefaultTransport
|
||||
|
||||
//transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
tn = time.Now()
|
||||
res, err := transport.RoundTrip(req)
|
||||
if err != nil {
|
||||
log.Error(err.Error(), "ruid", ruid)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Trace("http get response (feed)", "ruid", ruid, "api", endpoint, "topic", topic, "user", user, "code", res.StatusCode, "len", res.ContentLength)
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("expected status code %d, got %v (ruid %v)", 200, res.StatusCode, ruid)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
rdigest, err := digest(res.Body)
|
||||
if err != nil {
|
||||
log.Warn(err.Error(), "ruid", ruid)
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(rdigest, original) {
|
||||
err := fmt.Errorf("downloaded imported file md5=%x is not the same as the generated one=%x", rdigest, original)
|
||||
log.Warn(err.Error(), "ruid", ruid)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Trace("downloaded file matches random file", "ruid", ruid, "len", res.ContentLength)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetch is getting the requested `hash` from the `endpoint` and compares it with the `original` file
|
||||
func fetch(hash string, endpoint string, original []byte, ruid string) error {
|
||||
ctx, sp := spancontext.StartSpan(context.Background(), "upload-and-sync.fetch")
|
||||
defer sp.Finish()
|
||||
|
||||
log.Info("http get request", "ruid", ruid, "endpoint", endpoint, "hash", hash)
|
||||
|
||||
var tn time.Time
|
||||
reqUri := endpoint + "/bzz:/" + hash + "/"
|
||||
req, _ := http.NewRequest("GET", reqUri, nil)
|
||||
|
||||
opentracing.GlobalTracer().Inject(
|
||||
sp.Context(),
|
||||
opentracing.HTTPHeaders,
|
||||
opentracing.HTTPHeadersCarrier(req.Header))
|
||||
|
||||
trace := client.GetClientTrace(commandName+" - http get", commandName, ruid, &tn)
|
||||
|
||||
req = req.WithContext(httptrace.WithClientTrace(ctx, trace))
|
||||
transport := http.DefaultTransport
|
||||
|
||||
//transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
tn = time.Now()
|
||||
res, err := transport.RoundTrip(req)
|
||||
if err != nil {
|
||||
log.Error(err.Error(), "ruid", ruid)
|
||||
return err
|
||||
}
|
||||
log.Info("http get response", "ruid", ruid, "endpoint", endpoint, "hash", hash, "code", res.StatusCode, "len", res.ContentLength)
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
err := fmt.Errorf("expected status code %d, got %v", 200, res.StatusCode)
|
||||
log.Warn(err.Error(), "ruid", ruid)
|
||||
return err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
rdigest, err := digest(res.Body)
|
||||
if err != nil {
|
||||
log.Warn(err.Error(), "ruid", ruid)
|
||||
return err
|
||||
}
|
||||
|
||||
if !bytes.Equal(rdigest, original) {
|
||||
err := fmt.Errorf("downloaded imported file md5=%x is not the same as the generated one=%x", rdigest, original)
|
||||
log.Warn(err.Error(), "ruid", ruid)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Trace("downloaded file matches random file", "ruid", ruid, "len", res.ContentLength)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// upload an arbitrary byte as a plaintext file to `endpoint` using the api client
|
||||
func upload(data []byte, endpoint string) (string, error) {
|
||||
swarm := client.NewClient(endpoint)
|
||||
f := &client.File{
|
||||
ReadCloser: ioutil.NopCloser(bytes.NewReader(data)),
|
||||
ManifestEntry: api.ManifestEntry{
|
||||
ContentType: "text/plain",
|
||||
Mode: 0660,
|
||||
Size: int64(len(data)),
|
||||
},
|
||||
}
|
||||
|
||||
// upload data to bzz:// and retrieve the content-addressed manifest hash, hex-encoded.
|
||||
return swarm.Upload(f, "", false)
|
||||
}
|
||||
|
||||
func digest(r io.Reader) ([]byte, error) {
|
||||
h := md5.New()
|
||||
_, err := io.Copy(h, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
// generates random data in heap buffer
|
||||
func generateRandomData(datasize int) ([]byte, error) {
|
||||
b := make([]byte, datasize)
|
||||
c, err := crand.Read(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if c != datasize {
|
||||
return nil, errors.New("short read")
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||
"github.com/ethereum/go-ethereum/swarm/network"
|
||||
"github.com/ethereum/go-ethereum/swarm/network/simulation"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// create is used as the entry function for "create" app command.
|
||||
func create(ctx *cli.Context) error {
|
||||
log.PrintOrigins(true)
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(ctx.Int("verbosity")), log.StreamHandler(os.Stdout, log.TerminalFormat(true))))
|
||||
|
||||
if len(ctx.Args()) < 1 {
|
||||
return errors.New("argument should be the filename to verify or write-to")
|
||||
}
|
||||
filename, err := touchPath(ctx.Args()[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return createSnapshot(filename, ctx.Int("nodes"), strings.Split(ctx.String("services"), ","))
|
||||
}
|
||||
|
||||
// createSnapshot creates a new snapshot on filesystem with provided filename,
|
||||
// number of nodes and service names.
|
||||
func createSnapshot(filename string, nodes int, services []string) (err error) {
|
||||
log.Debug("create snapshot", "filename", filename, "nodes", nodes, "services", services)
|
||||
|
||||
sim := simulation.New(map[string]simulation.ServiceFunc{
|
||||
"bzz": func(ctx *adapters.ServiceContext, bucket *sync.Map) (node.Service, func(), error) {
|
||||
addr := network.NewAddr(ctx.Config.Node())
|
||||
kad := network.NewKademlia(addr.Over(), network.NewKadParams())
|
||||
hp := network.NewHiveParams()
|
||||
hp.KeepAliveInterval = time.Duration(200) * time.Millisecond
|
||||
hp.Discovery = true // discovery must be enabled when creating a snapshot
|
||||
|
||||
// store the kademlia in the bucket, needed later in the WaitTillHealthy function
|
||||
bucket.Store(simulation.BucketKeyKademlia, kad)
|
||||
|
||||
config := &network.BzzConfig{
|
||||
OverlayAddr: addr.Over(),
|
||||
UnderlayAddr: addr.Under(),
|
||||
HiveParams: hp,
|
||||
}
|
||||
return network.NewBzz(config, kad, nil, nil, nil), nil, nil
|
||||
},
|
||||
})
|
||||
defer sim.Close()
|
||||
|
||||
ids, err := sim.AddNodes(nodes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add nodes: %v", err)
|
||||
}
|
||||
|
||||
err = sim.Net.ConnectNodesRing(ids)
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect nodes: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancelSimRun := context.WithTimeout(context.Background(), 3*time.Minute)
|
||||
defer cancelSimRun()
|
||||
if _, err := sim.WaitTillHealthy(ctx); err != nil {
|
||||
return fmt.Errorf("wait for healthy kademlia: %v", err)
|
||||
}
|
||||
|
||||
var snap *simulations.Snapshot
|
||||
if len(services) > 0 {
|
||||
// If service names are provided, include them in the snapshot.
|
||||
// But, check if "bzz" service is not among them to remove it
|
||||
// form the snapshot as it exists on snapshot creation.
|
||||
var removeServices []string
|
||||
var wantBzz bool
|
||||
for _, s := range services {
|
||||
if s == "bzz" {
|
||||
wantBzz = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !wantBzz {
|
||||
removeServices = []string{"bzz"}
|
||||
}
|
||||
snap, err = sim.Net.SnapshotWithServices(services, removeServices)
|
||||
} else {
|
||||
snap, err = sim.Net.Snapshot()
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("create snapshot: %v", err)
|
||||
}
|
||||
jsonsnapshot, err := json.Marshal(snap)
|
||||
if err != nil {
|
||||
return fmt.Errorf("json encode snapshot: %v", err)
|
||||
}
|
||||
return ioutil.WriteFile(filename, jsonsnapshot, 0666)
|
||||
}
|
||||
|
||||
// touchPath creates an empty file and all subdirectories
|
||||
// that are missing.
|
||||
func touchPath(filename string) (string, error) {
|
||||
if path.IsAbs(filename) {
|
||||
if _, err := os.Stat(filename); err == nil {
|
||||
// path exists, overwrite
|
||||
return filename, nil
|
||||
}
|
||||
}
|
||||
|
||||
d, f := path.Split(filename)
|
||||
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = os.Stat(path.Join(dir, filename))
|
||||
if err == nil {
|
||||
// path exists, overwrite
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
dirPath := path.Join(dir, d)
|
||||
filePath := path.Join(dirPath, f)
|
||||
if d != "" {
|
||||
err = os.MkdirAll(dirPath, os.ModeDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return filePath, nil
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
||||
)
|
||||
|
||||
// TestSnapshotCreate is a high level e2e test that tests for snapshot generation.
|
||||
// It runs a few "create" commands with different flag values and loads generated
|
||||
// snapshot files to validate their content.
|
||||
func TestSnapshotCreate(t *testing.T) {
|
||||
t.Skip("test is flaky. disabling until underlying problem is addressed")
|
||||
|
||||
for _, v := range []struct {
|
||||
name string
|
||||
nodes int
|
||||
services string
|
||||
}{
|
||||
{
|
||||
name: "defaults",
|
||||
},
|
||||
{
|
||||
name: "more nodes",
|
||||
nodes: defaultNodes + 4,
|
||||
},
|
||||
{
|
||||
name: "services",
|
||||
services: "stream,pss,zorglub",
|
||||
},
|
||||
{
|
||||
name: "services with bzz",
|
||||
services: "bzz,pss",
|
||||
},
|
||||
} {
|
||||
t.Run(v.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
file, err := ioutil.TempFile("", "swarm-snapshot")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
if err = file.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
args := []string{"create"}
|
||||
if v.nodes > 0 {
|
||||
args = append(args, "--nodes", strconv.Itoa(v.nodes))
|
||||
}
|
||||
if v.services != "" {
|
||||
args = append(args, "--services", v.services)
|
||||
}
|
||||
testCmd := runSnapshot(t, append(args, file.Name())...)
|
||||
|
||||
testCmd.WaitExit()
|
||||
if code := testCmd.ExitStatus(); code != 0 {
|
||||
t.Fatalf("command exit code %v, expected 0", code)
|
||||
}
|
||||
|
||||
f, err := os.Open(file.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err := f.Close()
|
||||
if err != nil {
|
||||
t.Error("closing snapshot file", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
b, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var snap simulations.Snapshot
|
||||
err = json.Unmarshal(b, &snap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wantNodes := v.nodes
|
||||
if wantNodes == 0 {
|
||||
wantNodes = defaultNodes
|
||||
}
|
||||
gotNodes := len(snap.Nodes)
|
||||
if gotNodes != wantNodes {
|
||||
t.Errorf("got %v nodes, want %v", gotNodes, wantNodes)
|
||||
}
|
||||
|
||||
if len(snap.Conns) == 0 {
|
||||
t.Error("no connections in a snapshot")
|
||||
}
|
||||
|
||||
var wantServices []string
|
||||
if v.services != "" {
|
||||
wantServices = strings.Split(v.services, ",")
|
||||
} else {
|
||||
wantServices = []string{"bzz"}
|
||||
}
|
||||
// sort service names so they can be comparable
|
||||
// as strings to every node sorted services
|
||||
sort.Strings(wantServices)
|
||||
|
||||
for i, n := range snap.Nodes {
|
||||
gotServices := n.Node.Config.Services
|
||||
sort.Strings(gotServices)
|
||||
if fmt.Sprint(gotServices) != fmt.Sprint(wantServices) {
|
||||
t.Errorf("got services %v for node %v, want %v", gotServices, i, wantServices)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
||||
var gitDate string
|
||||
|
||||
// default value for "create" command --nodes flag
|
||||
const defaultNodes = 8
|
||||
|
||||
func main() {
|
||||
err := newApp().Run(os.Args)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// newApp construct a new instance of Swarm Snapshot Utility.
|
||||
// Method Run is called on it in the main function and in tests.
|
||||
func newApp() (app *cli.App) {
|
||||
app = utils.NewApp(gitCommit, gitDate, "Swarm Snapshot Utility")
|
||||
|
||||
app.Name = "swarm-snapshot"
|
||||
app.Usage = ""
|
||||
|
||||
// app flags (for all commands)
|
||||
app.Flags = []cli.Flag{
|
||||
cli.IntFlag{
|
||||
Name: "verbosity",
|
||||
Value: 1,
|
||||
Usage: "verbosity level",
|
||||
},
|
||||
}
|
||||
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "create",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "create a swarm snapshot",
|
||||
Action: create,
|
||||
// Flags only for "create" command.
|
||||
// Allow app flags to be specified after the
|
||||
// command argument.
|
||||
Flags: append(app.Flags,
|
||||
cli.IntFlag{
|
||||
Name: "nodes",
|
||||
Value: defaultNodes,
|
||||
Usage: "number of nodes",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "services",
|
||||
Value: "bzz",
|
||||
Usage: "comma separated list of services to boot the nodes with",
|
||||
},
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/reexec"
|
||||
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
||||
)
|
||||
|
||||
func init() {
|
||||
reexec.Register("swarm-snapshot", func() {
|
||||
if err := newApp().Run(os.Args); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
})
|
||||
}
|
||||
|
||||
func runSnapshot(t *testing.T, args ...string) *cmdtest.TestCmd {
|
||||
tt := cmdtest.NewTestCmd(t, nil)
|
||||
tt.Run("swarm-snapshot", args...)
|
||||
return tt
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if reexec.Init() {
|
||||
return
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
1390
cmd/swarm/testdata/datastore_fixture.go
vendored
1390
cmd/swarm/testdata/datastore_fixture.go
vendored
File diff suppressed because it is too large
Load diff
|
|
@ -1,188 +0,0 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Command bzzup uploads files to the swarm HTTP API.
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var upCommand = cli.Command{
|
||||
Action: upload,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "up",
|
||||
Usage: "uploads a file or directory to swarm using the HTTP API",
|
||||
ArgsUsage: "<file>",
|
||||
Flags: []cli.Flag{SwarmEncryptedFlag},
|
||||
Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash",
|
||||
}
|
||||
|
||||
func upload(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
var (
|
||||
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
recursive = ctx.GlobalBool(SwarmRecursiveFlag.Name)
|
||||
wantManifest = ctx.GlobalBoolT(SwarmWantManifestFlag.Name)
|
||||
defaultPath = ctx.GlobalString(SwarmUploadDefaultPath.Name)
|
||||
fromStdin = ctx.GlobalBool(SwarmUpFromStdinFlag.Name)
|
||||
mimeType = ctx.GlobalString(SwarmUploadMimeType.Name)
|
||||
client = swarm.NewClient(bzzapi)
|
||||
toEncrypt = ctx.Bool(SwarmEncryptedFlag.Name)
|
||||
autoDefaultPath = false
|
||||
file string
|
||||
)
|
||||
if autoDefaultPathString := os.Getenv(SwarmAutoDefaultPath); autoDefaultPathString != "" {
|
||||
b, err := strconv.ParseBool(autoDefaultPathString)
|
||||
if err != nil {
|
||||
utils.Fatalf("invalid environment variable %s: %v", SwarmAutoDefaultPath, err)
|
||||
}
|
||||
autoDefaultPath = b
|
||||
}
|
||||
if len(args) != 1 {
|
||||
if fromStdin {
|
||||
tmp, err := ioutil.TempFile("", "swarm-stdin")
|
||||
if err != nil {
|
||||
utils.Fatalf("error create tempfile: %s", err)
|
||||
}
|
||||
defer os.Remove(tmp.Name())
|
||||
n, err := io.Copy(tmp, os.Stdin)
|
||||
if err != nil {
|
||||
utils.Fatalf("error copying stdin to tempfile: %s", err)
|
||||
} else if n == 0 {
|
||||
utils.Fatalf("error reading from stdin: zero length")
|
||||
}
|
||||
file = tmp.Name()
|
||||
} else {
|
||||
utils.Fatalf("Need filename as the first and only argument")
|
||||
}
|
||||
} else {
|
||||
file = expandPath(args[0])
|
||||
}
|
||||
|
||||
if !wantManifest {
|
||||
f, err := swarm.Open(file)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error opening file: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
hash, err := client.UploadRaw(f, f.Size, toEncrypt)
|
||||
if err != nil {
|
||||
utils.Fatalf("Upload failed: %s", err)
|
||||
}
|
||||
fmt.Println(hash)
|
||||
return
|
||||
}
|
||||
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
utils.Fatalf("Error opening file: %s", err)
|
||||
}
|
||||
|
||||
// define a function which either uploads a directory or single file
|
||||
// based on the type of the file being uploaded
|
||||
var doUpload func() (hash string, err error)
|
||||
if stat.IsDir() {
|
||||
doUpload = func() (string, error) {
|
||||
if !recursive {
|
||||
return "", errors.New("Argument is a directory and recursive upload is disabled")
|
||||
}
|
||||
if autoDefaultPath && defaultPath == "" {
|
||||
defaultEntryCandidate := path.Join(file, "index.html")
|
||||
log.Debug("trying to find default path", "path", defaultEntryCandidate)
|
||||
defaultEntryStat, err := os.Stat(defaultEntryCandidate)
|
||||
if err == nil && !defaultEntryStat.IsDir() {
|
||||
log.Debug("setting auto detected default path", "path", defaultEntryCandidate)
|
||||
defaultPath = defaultEntryCandidate
|
||||
}
|
||||
}
|
||||
if defaultPath != "" {
|
||||
// construct absolute default path
|
||||
absDefaultPath, _ := filepath.Abs(defaultPath)
|
||||
absFile, _ := filepath.Abs(file)
|
||||
// make sure absolute directory ends with only one "/"
|
||||
// to trim it from absolute default path and get relative default path
|
||||
absFile = strings.TrimRight(absFile, "/") + "/"
|
||||
if absDefaultPath != "" && absFile != "" && strings.HasPrefix(absDefaultPath, absFile) {
|
||||
defaultPath = strings.TrimPrefix(absDefaultPath, absFile)
|
||||
}
|
||||
}
|
||||
return client.UploadDirectory(file, defaultPath, "", toEncrypt)
|
||||
}
|
||||
} else {
|
||||
doUpload = func() (string, error) {
|
||||
f, err := swarm.Open(file)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error opening file: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
if mimeType != "" {
|
||||
f.ContentType = mimeType
|
||||
}
|
||||
return client.Upload(f, "", toEncrypt)
|
||||
}
|
||||
}
|
||||
hash, err := doUpload()
|
||||
if err != nil {
|
||||
utils.Fatalf("Upload failed: %s", err)
|
||||
}
|
||||
fmt.Println(hash)
|
||||
}
|
||||
|
||||
// Expands a file path
|
||||
// 1. replace tilde with users home dir
|
||||
// 2. expands embedded environment variables
|
||||
// 3. cleans the path, e.g. /a/b/../c -> /a/c
|
||||
// Note, it has limitations, e.g. ~someuser/tmp will not be expanded
|
||||
func expandPath(p string) string {
|
||||
if i := strings.Index(p, ":"); i > 0 {
|
||||
return p
|
||||
}
|
||||
if i := strings.Index(p, "@"); i > 0 {
|
||||
return p
|
||||
}
|
||||
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
|
||||
if home := homeDir(); home != "" {
|
||||
p = home + p[1:]
|
||||
}
|
||||
}
|
||||
return path.Clean(os.ExpandEnv(p))
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
if usr, err := user.Current(); err == nil {
|
||||
return usr.HomeDir
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -1,359 +0,0 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
swarmapi "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||
"github.com/mattn/go-colorable"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.PrintOrigins(true)
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
|
||||
}
|
||||
|
||||
func TestSwarmUp(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
cluster := newTestCluster(t, clusterSize)
|
||||
defer cluster.Shutdown()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
f func(t *testing.T, cluster *testCluster)
|
||||
}{
|
||||
{"NoEncryption", testNoEncryption},
|
||||
{"Encrypted", testEncrypted},
|
||||
{"RecursiveNoEncryption", testRecursiveNoEncryption},
|
||||
{"RecursiveEncrypted", testRecursiveEncrypted},
|
||||
{"DefaultPathAll", testDefaultPathAll},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.f(t, cluster)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// testNoEncryption tests that running 'swarm up' makes the resulting file
|
||||
// available from all nodes via the HTTP API
|
||||
func testNoEncryption(t *testing.T, cluster *testCluster) {
|
||||
testDefault(t, cluster, false)
|
||||
}
|
||||
|
||||
// testEncrypted tests that running 'swarm up --encrypted' makes the resulting file
|
||||
// available from all nodes via the HTTP API
|
||||
func testEncrypted(t *testing.T, cluster *testCluster) {
|
||||
testDefault(t, cluster, true)
|
||||
}
|
||||
|
||||
func testRecursiveNoEncryption(t *testing.T, cluster *testCluster) {
|
||||
testRecursive(t, cluster, false)
|
||||
}
|
||||
|
||||
func testRecursiveEncrypted(t *testing.T, cluster *testCluster) {
|
||||
testRecursive(t, cluster, true)
|
||||
}
|
||||
|
||||
func testDefault(t *testing.T, cluster *testCluster, toEncrypt bool) {
|
||||
tmpFileName := testutil.TempFileWithContent(t, data)
|
||||
defer os.Remove(tmpFileName)
|
||||
|
||||
// write data to file
|
||||
hashRegexp := `[a-f\d]{64}`
|
||||
flags := []string{
|
||||
"--bzzapi", cluster.Nodes[0].URL,
|
||||
"up",
|
||||
tmpFileName}
|
||||
if toEncrypt {
|
||||
hashRegexp = `[a-f\d]{128}`
|
||||
flags = []string{
|
||||
"--bzzapi", cluster.Nodes[0].URL,
|
||||
"up",
|
||||
"--encrypt",
|
||||
tmpFileName}
|
||||
}
|
||||
// upload the file with 'swarm up' and expect a hash
|
||||
log.Info(fmt.Sprintf("uploading file with 'swarm up'"))
|
||||
up := runSwarm(t, flags...)
|
||||
_, matches := up.ExpectRegexp(hashRegexp)
|
||||
up.ExpectExit()
|
||||
hash := matches[0]
|
||||
log.Info("file uploaded", "hash", hash)
|
||||
|
||||
// get the file from the HTTP API of each node
|
||||
for _, node := range cluster.Nodes {
|
||||
log.Info("getting file from node", "node", node.Name)
|
||||
|
||||
res, err := http.Get(node.URL + "/bzz:/" + hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
reply, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
t.Fatalf("expected HTTP status 200, got %s", res.Status)
|
||||
}
|
||||
if string(reply) != data {
|
||||
t.Fatalf("expected HTTP body %q, got %q", data, reply)
|
||||
}
|
||||
log.Debug("verifying uploaded file using `swarm down`")
|
||||
//try to get the content with `swarm down`
|
||||
tmpDownload, err := ioutil.TempDir("", "swarm-test")
|
||||
tmpDownload = path.Join(tmpDownload, "tmpfile.tmp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDownload)
|
||||
|
||||
bzzLocator := "bzz:/" + hash
|
||||
flags = []string{
|
||||
"--bzzapi", cluster.Nodes[0].URL,
|
||||
"down",
|
||||
bzzLocator,
|
||||
tmpDownload,
|
||||
}
|
||||
|
||||
down := runSwarm(t, flags...)
|
||||
down.ExpectExit()
|
||||
|
||||
fi, err := os.Stat(tmpDownload)
|
||||
if err != nil {
|
||||
t.Fatalf("could not stat path: %v", err)
|
||||
}
|
||||
|
||||
switch mode := fi.Mode(); {
|
||||
case mode.IsRegular():
|
||||
downloadedBytes, err := ioutil.ReadFile(tmpDownload)
|
||||
if err != nil {
|
||||
t.Fatalf("had an error reading the downloaded file: %v", err)
|
||||
}
|
||||
if !bytes.Equal(downloadedBytes, bytes.NewBufferString(data).Bytes()) {
|
||||
t.Fatalf("retrieved data and posted data not equal!")
|
||||
}
|
||||
|
||||
default:
|
||||
t.Fatalf("expected to download regular file, got %s", fi.Mode())
|
||||
}
|
||||
}
|
||||
|
||||
timeout := time.Duration(2 * time.Second)
|
||||
httpClient := http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
// try to squeeze a timeout by getting an non-existent hash from each node
|
||||
for _, node := range cluster.Nodes {
|
||||
_, err := httpClient.Get(node.URL + "/bzz:/1023e8bae0f70be7d7b5f74343088ba408a218254391490c85ae16278e230340")
|
||||
// we're speeding up the timeout here since netstore has a 60 seconds timeout on a request
|
||||
if err != nil && !strings.Contains(err.Error(), "Client.Timeout exceeded while awaiting headers") {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// this is disabled since it takes 60s due to netstore timeout
|
||||
// if res.StatusCode != 404 {
|
||||
// t.Fatalf("expected HTTP status 404, got %s", res.Status)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
func testRecursive(t *testing.T, cluster *testCluster, toEncrypt bool) {
|
||||
tmpUploadDir, err := ioutil.TempDir("", "swarm-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpUploadDir)
|
||||
// create tmp files
|
||||
for _, path := range []string{"tmp1", "tmp2"} {
|
||||
if err := ioutil.WriteFile(filepath.Join(tmpUploadDir, path), bytes.NewBufferString(data).Bytes(), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
hashRegexp := `[a-f\d]{64}`
|
||||
flags := []string{
|
||||
"--bzzapi", cluster.Nodes[0].URL,
|
||||
"--recursive",
|
||||
"up",
|
||||
tmpUploadDir}
|
||||
if toEncrypt {
|
||||
hashRegexp = `[a-f\d]{128}`
|
||||
flags = []string{
|
||||
"--bzzapi", cluster.Nodes[0].URL,
|
||||
"--recursive",
|
||||
"up",
|
||||
"--encrypt",
|
||||
tmpUploadDir}
|
||||
}
|
||||
// upload the file with 'swarm up' and expect a hash
|
||||
log.Info(fmt.Sprintf("uploading file with 'swarm up'"))
|
||||
up := runSwarm(t, flags...)
|
||||
_, matches := up.ExpectRegexp(hashRegexp)
|
||||
up.ExpectExit()
|
||||
hash := matches[0]
|
||||
log.Info("dir uploaded", "hash", hash)
|
||||
|
||||
// get the file from the HTTP API of each node
|
||||
for _, node := range cluster.Nodes {
|
||||
log.Info("getting file from node", "node", node.Name)
|
||||
//try to get the content with `swarm down`
|
||||
tmpDownload, err := ioutil.TempDir("", "swarm-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDownload)
|
||||
bzzLocator := "bzz:/" + hash
|
||||
flagss := []string{
|
||||
"--bzzapi", cluster.Nodes[0].URL,
|
||||
"down",
|
||||
"--recursive",
|
||||
bzzLocator,
|
||||
tmpDownload,
|
||||
}
|
||||
|
||||
fmt.Println("downloading from swarm with recursive")
|
||||
down := runSwarm(t, flagss...)
|
||||
down.ExpectExit()
|
||||
|
||||
files, err := ioutil.ReadDir(tmpDownload)
|
||||
for _, v := range files {
|
||||
fi, err := os.Stat(path.Join(tmpDownload, v.Name()))
|
||||
if err != nil {
|
||||
t.Fatalf("got an error: %v", err)
|
||||
}
|
||||
|
||||
switch mode := fi.Mode(); {
|
||||
case mode.IsRegular():
|
||||
if file, err := swarmapi.Open(path.Join(tmpDownload, v.Name())); err != nil {
|
||||
t.Fatalf("encountered an error opening the file returned from the CLI: %v", err)
|
||||
} else {
|
||||
ff := make([]byte, len(data))
|
||||
io.ReadFull(file, ff)
|
||||
buf := bytes.NewBufferString(data)
|
||||
|
||||
if !bytes.Equal(ff, buf.Bytes()) {
|
||||
t.Fatalf("retrieved data and posted data not equal!")
|
||||
}
|
||||
}
|
||||
default:
|
||||
t.Fatalf("this shouldnt happen")
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("could not list files at: %v", files)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// testDefaultPathAll tests swarm recursive upload with relative and absolute
|
||||
// default paths and with encryption.
|
||||
func testDefaultPathAll(t *testing.T, cluster *testCluster) {
|
||||
testDefaultPath(t, cluster, false, false)
|
||||
testDefaultPath(t, cluster, false, true)
|
||||
testDefaultPath(t, cluster, true, false)
|
||||
testDefaultPath(t, cluster, true, true)
|
||||
}
|
||||
|
||||
func testDefaultPath(t *testing.T, cluster *testCluster, toEncrypt bool, absDefaultPath bool) {
|
||||
tmp, err := ioutil.TempDir("", "swarm-defaultpath-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
err = ioutil.WriteFile(filepath.Join(tmp, "index.html"), []byte("<h1>Test</h1>"), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(tmp, "robots.txt"), []byte("Disallow: /"), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defaultPath := "index.html"
|
||||
if absDefaultPath {
|
||||
defaultPath = filepath.Join(tmp, defaultPath)
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"--bzzapi",
|
||||
cluster.Nodes[0].URL,
|
||||
"--recursive",
|
||||
"--defaultpath",
|
||||
defaultPath,
|
||||
"up",
|
||||
tmp,
|
||||
}
|
||||
if toEncrypt {
|
||||
args = append(args, "--encrypt")
|
||||
}
|
||||
|
||||
up := runSwarm(t, args...)
|
||||
hashRegexp := `[a-f\d]{64,128}`
|
||||
_, matches := up.ExpectRegexp(hashRegexp)
|
||||
up.ExpectExit()
|
||||
hash := matches[0]
|
||||
|
||||
client := swarmapi.NewClient(cluster.Nodes[0].URL)
|
||||
|
||||
m, isEncrypted, err := client.DownloadManifest(hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if toEncrypt != isEncrypted {
|
||||
t.Error("downloaded manifest is not encrypted")
|
||||
}
|
||||
|
||||
var found bool
|
||||
var entriesCount int
|
||||
for _, e := range m.Entries {
|
||||
entriesCount++
|
||||
if e.Path == "" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Error("manifest default entry was not found")
|
||||
}
|
||||
|
||||
if entriesCount != 3 {
|
||||
t.Errorf("manifest contains %v entries, expected %v", entriesCount, 3)
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||
pcsclite "github.com/gballet/go-libpcsclite"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
|
|
@ -130,6 +131,11 @@ var (
|
|||
Name: "nousb",
|
||||
Usage: "Disables monitoring for and managing USB hardware wallets",
|
||||
}
|
||||
SmartCardDaemonPathFlag = cli.StringFlag{
|
||||
Name: "pcscdpath",
|
||||
Usage: "Path to the smartcard daemon (pcscd) socket file",
|
||||
Value: pcsclite.PCSCDSockName,
|
||||
}
|
||||
NetworkIdFlag = cli.Uint64Flag{
|
||||
Name: "networkid",
|
||||
Usage: "Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten, 4=Rinkeby)",
|
||||
|
|
@ -206,13 +212,13 @@ var (
|
|||
}
|
||||
LightBandwidthInFlag = cli.IntFlag{
|
||||
Name: "lightbwin",
|
||||
Usage: "Incoming bandwidth limit for light server (1000 bytes/sec, 0 = unlimited)",
|
||||
Value: 1000,
|
||||
Usage: "Incoming bandwidth limit for light server (kilobytes/sec, 0 = unlimited)",
|
||||
Value: 0,
|
||||
}
|
||||
LightBandwidthOutFlag = cli.IntFlag{
|
||||
Name: "lightbwout",
|
||||
Usage: "Outgoing bandwidth limit for light server (1000 bytes/sec, 0 = unlimited)",
|
||||
Value: 5000,
|
||||
Usage: "Outgoing bandwidth limit for light server (kilobytes/sec, 0 = unlimited)",
|
||||
Value: 0,
|
||||
}
|
||||
LightPeersFlag = cli.IntFlag{
|
||||
Name: "lightpeers",
|
||||
|
|
@ -789,7 +795,7 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
|
|||
cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls))
|
||||
for _, url := range urls {
|
||||
if url != "" {
|
||||
node, err := enode.ParseV4(url)
|
||||
node, err := enode.Parse(enode.ValidSchemes, url)
|
||||
if err != nil {
|
||||
log.Crit("Bootstrap URL invalid", "enode", url, "err", err)
|
||||
continue
|
||||
|
|
@ -1127,6 +1133,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
|
|||
setWS(ctx, cfg)
|
||||
setNodeUserIdent(ctx, cfg)
|
||||
setDataDir(ctx, cfg)
|
||||
setSmartCard(ctx, cfg)
|
||||
|
||||
if ctx.GlobalIsSet(ExternalSignerFlag.Name) {
|
||||
cfg.ExternalSigner = ctx.GlobalString(ExternalSignerFlag.Name)
|
||||
|
|
@ -1146,6 +1153,26 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
func setSmartCard(ctx *cli.Context, cfg *node.Config) {
|
||||
// Skip enabling smartcards if no path is set
|
||||
path := ctx.GlobalString(SmartCardDaemonPathFlag.Name)
|
||||
if path == "" {
|
||||
return
|
||||
}
|
||||
// Sanity check that the smartcard path is valid
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
log.Info("Smartcard socket not found, disabling", "err", err)
|
||||
return
|
||||
}
|
||||
if fi.Mode()&os.ModeType != os.ModeSocket {
|
||||
log.Error("Invalid smartcard daemon path", "path", path, "type", fi.Mode().String())
|
||||
return
|
||||
}
|
||||
// Smartcard daemon path exists and is a socket, enable it
|
||||
cfg.SmartCardDaemonPath = path
|
||||
}
|
||||
|
||||
func setDataDir(ctx *cli.Context, cfg *node.Config) {
|
||||
switch {
|
||||
case ctx.GlobalIsSet(DataDirFlag.Name):
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ func initialize() {
|
|||
if len(*argEnode) == 0 {
|
||||
argEnode = scanLineA("Please enter the peer's enode: ")
|
||||
}
|
||||
peer := enode.MustParseV4(*argEnode)
|
||||
peer := enode.MustParse(*argEnode)
|
||||
peers = append(peers, peer)
|
||||
}
|
||||
|
||||
|
|
@ -747,9 +747,9 @@ func requestExpiredMessagesLoop() {
|
|||
}
|
||||
|
||||
func extractIDFromEnode(s string) []byte {
|
||||
n, err := enode.ParseV4(s)
|
||||
n, err := enode.Parse(enode.ValidSchemes, s)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to parse enode: %s", err)
|
||||
utils.Fatalf("Failed to parse node: %s", err)
|
||||
}
|
||||
return n.ID().Bytes()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,13 @@ const (
|
|||
// HexOrDecimal256 marshals big.Int as hex or decimal.
|
||||
type HexOrDecimal256 big.Int
|
||||
|
||||
// NewHexOrDecimal256 creates a new HexOrDecimal256
|
||||
func NewHexOrDecimal256(x int64) *HexOrDecimal256 {
|
||||
b := big.NewInt(x)
|
||||
h := HexOrDecimal256(*b)
|
||||
return &h
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (i *HexOrDecimal256) UnmarshalText(input []byte) error {
|
||||
bigint, ok := ParseBig256(string(input))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,20 @@
|
|||
// CookieJar - A contestant's algorithm toolbox
|
||||
// Copyright (c) 2013 Peter Szilagyi. All rights reserved.
|
||||
//
|
||||
// CookieJar is dual licensed: use of this source code is governed by a BSD
|
||||
// license that can be found in the LICENSE file. Alternatively, the CookieJar
|
||||
// toolbox may be used in accordance with the terms and conditions contained
|
||||
// in a signed written agreement between you and the author(s).
|
||||
|
||||
// This is a duplicated and slightly modified version of "gopkg.in/karalabe/cookiejar.v2/collections/prque".
|
||||
|
||||
// Package prque implements a priority queue data structure supporting arbitrary
|
||||
// value types and int64 priorities.
|
||||
//
|
||||
// If you would like to use a min-priority queue, simply negate the priorities.
|
||||
//
|
||||
// Internally the queue is based on the standard heap package working on a
|
||||
// sortable version of the block based stack.
|
||||
package prque
|
||||
|
||||
import (
|
||||
|
|
@ -11,8 +26,8 @@ type Prque struct {
|
|||
cont *sstack
|
||||
}
|
||||
|
||||
// Creates a new priority queue.
|
||||
func New(setIndex setIndexCallback) *Prque {
|
||||
// New creates a new priority queue.
|
||||
func New(setIndex SetIndexCallback) *Prque {
|
||||
return &Prque{newSstack(setIndex)}
|
||||
}
|
||||
|
||||
|
|
@ -21,6 +36,12 @@ func (p *Prque) Push(data interface{}, priority int64) {
|
|||
heap.Push(p.cont, &item{data, priority})
|
||||
}
|
||||
|
||||
// Peek returns the value with the greates priority but does not pop it off.
|
||||
func (p *Prque) Peek() (interface{}, int64) {
|
||||
item := p.cont.blocks[0][0]
|
||||
return item.value, item.priority
|
||||
}
|
||||
|
||||
// Pops the value with the greates priority off the stack and returns it.
|
||||
// Currently no shrinking is done.
|
||||
func (p *Prque) Pop() (interface{}, int64) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
// CookieJar - A contestant's algorithm toolbox
|
||||
// Copyright (c) 2013 Peter Szilagyi. All rights reserved.
|
||||
//
|
||||
// CookieJar is dual licensed: use of this source code is governed by a BSD
|
||||
// license that can be found in the LICENSE file. Alternatively, the CookieJar
|
||||
// toolbox may be used in accordance with the terms and conditions contained
|
||||
// in a signed written agreement between you and the author(s).
|
||||
|
||||
// This is a duplicated and slightly modified version of "gopkg.in/karalabe/cookiejar.v2/collections/prque".
|
||||
|
||||
package prque
|
||||
|
|
@ -14,16 +22,16 @@ type item struct {
|
|||
priority int64
|
||||
}
|
||||
|
||||
// setIndexCallback is called when the element is moved to a new index.
|
||||
// Providing setIndexCallback is optional, it is needed only if the application needs
|
||||
// SetIndexCallback is called when the element is moved to a new index.
|
||||
// Providing SetIndexCallback is optional, it is needed only if the application needs
|
||||
// to delete elements other than the top one.
|
||||
type setIndexCallback func(a interface{}, i int)
|
||||
type SetIndexCallback func(data interface{}, index int)
|
||||
|
||||
// Internal sortable stack data structure. Implements the Push and Pop ops for
|
||||
// the stack (heap) functionality and the Len, Less and Swap methods for the
|
||||
// sortability requirements of the heaps.
|
||||
type sstack struct {
|
||||
setIndex setIndexCallback
|
||||
setIndex SetIndexCallback
|
||||
size int
|
||||
capacity int
|
||||
offset int
|
||||
|
|
@ -33,7 +41,7 @@ type sstack struct {
|
|||
}
|
||||
|
||||
// Creates a new, empty stack.
|
||||
func newSstack(setIndex setIndexCallback) *sstack {
|
||||
func newSstack(setIndex SetIndexCallback) *sstack {
|
||||
result := new(sstack)
|
||||
result.setIndex = setIndex
|
||||
result.active = make([]*item, blockSize)
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package chequebook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
const Version = "1.0"
|
||||
|
||||
var errNoChequebook = errors.New("no chequebook")
|
||||
|
||||
type API struct {
|
||||
chequebookf func() *Chequebook
|
||||
}
|
||||
|
||||
func NewAPI(ch func() *Chequebook) *API {
|
||||
return &API{ch}
|
||||
}
|
||||
|
||||
func (a *API) Balance() (string, error) {
|
||||
ch := a.chequebookf()
|
||||
if ch == nil {
|
||||
return "", errNoChequebook
|
||||
}
|
||||
return ch.Balance().String(), nil
|
||||
}
|
||||
|
||||
func (a *API) Issue(beneficiary common.Address, amount *big.Int) (cheque *Cheque, err error) {
|
||||
ch := a.chequebookf()
|
||||
if ch == nil {
|
||||
return nil, errNoChequebook
|
||||
}
|
||||
return ch.Issue(beneficiary, amount)
|
||||
}
|
||||
|
||||
func (a *API) Cash(cheque *Cheque) (txhash string, err error) {
|
||||
ch := a.chequebookf()
|
||||
if ch == nil {
|
||||
return "", errNoChequebook
|
||||
}
|
||||
return ch.Cash(cheque)
|
||||
}
|
||||
|
||||
func (a *API) Deposit(amount *big.Int) (txhash string, err error) {
|
||||
ch := a.chequebookf()
|
||||
if ch == nil {
|
||||
return "", errNoChequebook
|
||||
}
|
||||
return ch.Deposit(amount)
|
||||
}
|
||||
|
|
@ -1,642 +0,0 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package chequebook package wraps the 'chequebook' Ethereum smart contract.
|
||||
//
|
||||
// The functions in this package allow using chequebook for
|
||||
// issuing, receiving, verifying cheques in ether; (auto)cashing cheques in ether
|
||||
// as well as (auto)depositing ether to the chequebook contract.
|
||||
package chequebook
|
||||
|
||||
//go:generate abigen --sol contract/chequebook.sol --exc contract/mortal.sol:mortal,contract/owned.sol:owned --pkg contract --out contract/chequebook.go
|
||||
//go:generate go run ./gencode.go
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/contracts/chequebook/contract"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/services/swap/swap"
|
||||
)
|
||||
|
||||
// TODO(zelig): watch peer solvency and notify of bouncing cheques
|
||||
// TODO(zelig): enable paying with cheque by signing off
|
||||
|
||||
// Some functionality requires interacting with the blockchain:
|
||||
// * setting current balance on peer's chequebook
|
||||
// * sending the transaction to cash the cheque
|
||||
// * depositing ether to the chequebook
|
||||
// * watching incoming ether
|
||||
|
||||
var (
|
||||
gasToCash = uint64(2000000) // gas cost of a cash transaction using chequebook
|
||||
// gasToDeploy = uint64(3000000)
|
||||
)
|
||||
|
||||
// Backend wraps all methods required for chequebook operation.
|
||||
type Backend interface {
|
||||
bind.ContractBackend
|
||||
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
|
||||
BalanceAt(ctx context.Context, address common.Address, blockNum *big.Int) (*big.Int, error)
|
||||
}
|
||||
|
||||
// Cheque represents a payment promise to a single beneficiary.
|
||||
type Cheque struct {
|
||||
Contract common.Address // address of chequebook, needed to avoid cross-contract submission
|
||||
Beneficiary common.Address
|
||||
Amount *big.Int // cumulative amount of all funds sent
|
||||
Sig []byte // signature Sign(Keccak256(contract, beneficiary, amount), prvKey)
|
||||
}
|
||||
|
||||
func (ch *Cheque) String() string {
|
||||
return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", ch.Contract.Hex(), ch.Beneficiary.Hex(), ch.Amount, ch.Sig)
|
||||
}
|
||||
|
||||
type Params struct {
|
||||
ContractCode, ContractAbi string
|
||||
}
|
||||
|
||||
var ContractParams = &Params{contract.ChequebookBin, contract.ChequebookABI}
|
||||
|
||||
// Chequebook can create and sign cheques from a single contract to multiple beneficiaries.
|
||||
// It is the outgoing payment handler for peer to peer micropayments.
|
||||
type Chequebook struct {
|
||||
path string // path to chequebook file
|
||||
prvKey *ecdsa.PrivateKey // private key to sign cheque with
|
||||
lock sync.Mutex //
|
||||
backend Backend // blockchain API
|
||||
quit chan bool // when closed causes autodeposit to stop
|
||||
owner common.Address // owner address (derived from pubkey)
|
||||
contract *contract.Chequebook // abigen binding
|
||||
session *contract.ChequebookSession // abigen binding with Tx Opts
|
||||
|
||||
// persisted fields
|
||||
balance *big.Int // not synced with blockchain
|
||||
contractAddr common.Address // contract address
|
||||
sent map[common.Address]*big.Int //tallies for beneficiaries
|
||||
|
||||
txhash string // tx hash of last deposit tx
|
||||
threshold *big.Int // threshold that triggers autodeposit if not nil
|
||||
buffer *big.Int // buffer to keep on top of balance for fork protection
|
||||
|
||||
log log.Logger // contextual logger with the contract address embedded
|
||||
}
|
||||
|
||||
func (cb *Chequebook) String() string {
|
||||
return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", cb.contractAddr.Hex(), cb.owner.Hex(), cb.balance, cb.prvKey.PublicKey)
|
||||
}
|
||||
|
||||
// NewChequebook creates a new Chequebook.
|
||||
func NewChequebook(path string, contractAddr common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (*Chequebook, error) {
|
||||
balance := new(big.Int)
|
||||
sent := make(map[common.Address]*big.Int)
|
||||
|
||||
chbook, err := contract.NewChequebook(contractAddr, backend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transactOpts := bind.NewKeyedTransactor(prvKey)
|
||||
session := &contract.ChequebookSession{
|
||||
Contract: chbook,
|
||||
TransactOpts: *transactOpts,
|
||||
}
|
||||
|
||||
cb := &Chequebook{
|
||||
prvKey: prvKey,
|
||||
balance: balance,
|
||||
contractAddr: contractAddr,
|
||||
sent: sent,
|
||||
path: path,
|
||||
backend: backend,
|
||||
owner: transactOpts.From,
|
||||
contract: chbook,
|
||||
session: session,
|
||||
log: log.New("contract", contractAddr),
|
||||
}
|
||||
if (contractAddr != common.Address{}) {
|
||||
cb.setBalanceFromBlockChain()
|
||||
cb.log.Trace("New chequebook initialised", "owner", cb.owner, "balance", cb.balance)
|
||||
}
|
||||
return cb, nil
|
||||
}
|
||||
|
||||
func (cb *Chequebook) setBalanceFromBlockChain() {
|
||||
balance, err := cb.backend.BalanceAt(context.TODO(), cb.contractAddr, nil)
|
||||
if err != nil {
|
||||
log.Error("Failed to retrieve chequebook balance", "err", err)
|
||||
} else {
|
||||
cb.balance.Set(balance)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadChequebook loads a chequebook from disk (file path).
|
||||
func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend, checkBalance bool) (*Chequebook, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cb, _ := NewChequebook(path, common.Address{}, prvKey, backend)
|
||||
|
||||
if err = json.Unmarshal(data, cb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if checkBalance {
|
||||
cb.setBalanceFromBlockChain()
|
||||
}
|
||||
log.Trace("Loaded chequebook from disk", "path", path)
|
||||
|
||||
return cb, nil
|
||||
}
|
||||
|
||||
// chequebookFile is the JSON representation of a chequebook.
|
||||
type chequebookFile struct {
|
||||
Balance string
|
||||
Contract string
|
||||
Owner string
|
||||
Sent map[string]string
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserialises a chequebook.
|
||||
func (cb *Chequebook) UnmarshalJSON(data []byte) error {
|
||||
var file chequebookFile
|
||||
err := json.Unmarshal(data, &file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, ok := cb.balance.SetString(file.Balance, 10)
|
||||
if !ok {
|
||||
return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance)
|
||||
}
|
||||
cb.contractAddr = common.HexToAddress(file.Contract)
|
||||
for addr, sent := range file.Sent {
|
||||
cb.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10)
|
||||
if !ok {
|
||||
return fmt.Errorf("beneficiary %v cumulative amount sent: unable to convert string to big integer: %v", addr, sent)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON serialises a chequebook.
|
||||
func (cb *Chequebook) MarshalJSON() ([]byte, error) {
|
||||
var file = &chequebookFile{
|
||||
Balance: cb.balance.String(),
|
||||
Contract: cb.contractAddr.Hex(),
|
||||
Owner: cb.owner.Hex(),
|
||||
Sent: make(map[string]string),
|
||||
}
|
||||
for addr, sent := range cb.sent {
|
||||
file.Sent[addr.Hex()] = sent.String()
|
||||
}
|
||||
return json.Marshal(file)
|
||||
}
|
||||
|
||||
// Save persists the chequebook on disk, remembering balance, contract address and
|
||||
// cumulative amount of funds sent for each beneficiary.
|
||||
func (cb *Chequebook) Save() error {
|
||||
data, err := json.MarshalIndent(cb, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cb.log.Trace("Saving chequebook to disk", cb.path)
|
||||
|
||||
return ioutil.WriteFile(cb.path, data, os.ModePerm)
|
||||
}
|
||||
|
||||
// Stop quits the autodeposit go routine to terminate
|
||||
func (cb *Chequebook) Stop() {
|
||||
defer cb.lock.Unlock()
|
||||
cb.lock.Lock()
|
||||
if cb.quit != nil {
|
||||
close(cb.quit)
|
||||
cb.quit = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Issue creates a cheque signed by the chequebook owner's private key. The
|
||||
// signer commits to a contract (one that they own), a beneficiary and amount.
|
||||
func (cb *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (*Cheque, error) {
|
||||
defer cb.lock.Unlock()
|
||||
cb.lock.Lock()
|
||||
|
||||
if amount.Sign() <= 0 {
|
||||
return nil, fmt.Errorf("amount must be greater than zero (%v)", amount)
|
||||
}
|
||||
var (
|
||||
ch *Cheque
|
||||
err error
|
||||
)
|
||||
if cb.balance.Cmp(amount) < 0 {
|
||||
err = fmt.Errorf("insufficient funds to issue cheque for amount: %v. balance: %v", amount, cb.balance)
|
||||
} else {
|
||||
var sig []byte
|
||||
sent, found := cb.sent[beneficiary]
|
||||
if !found {
|
||||
sent = new(big.Int)
|
||||
cb.sent[beneficiary] = sent
|
||||
}
|
||||
sum := new(big.Int).Set(sent)
|
||||
sum.Add(sum, amount)
|
||||
|
||||
sig, err = crypto.Sign(sigHash(cb.contractAddr, beneficiary, sum), cb.prvKey)
|
||||
if err == nil {
|
||||
ch = &Cheque{
|
||||
Contract: cb.contractAddr,
|
||||
Beneficiary: beneficiary,
|
||||
Amount: sum,
|
||||
Sig: sig,
|
||||
}
|
||||
sent.Set(sum)
|
||||
cb.balance.Sub(cb.balance, amount) // subtract amount from balance
|
||||
}
|
||||
}
|
||||
// auto deposit if threshold is set and balance is less then threshold
|
||||
// note this is called even if issuing cheque fails
|
||||
// so we reattempt depositing
|
||||
if cb.threshold != nil {
|
||||
if cb.balance.Cmp(cb.threshold) < 0 {
|
||||
send := new(big.Int).Sub(cb.buffer, cb.balance)
|
||||
cb.deposit(send)
|
||||
}
|
||||
}
|
||||
return ch, err
|
||||
}
|
||||
|
||||
// Cash is a convenience method to cash any cheque.
|
||||
func (cb *Chequebook) Cash(ch *Cheque) (string, error) {
|
||||
return ch.Cash(cb.session)
|
||||
}
|
||||
|
||||
// data to sign: contract address, beneficiary, cumulative amount of funds ever sent
|
||||
func sigHash(contract, beneficiary common.Address, sum *big.Int) []byte {
|
||||
bigamount := sum.Bytes()
|
||||
if len(bigamount) > 32 {
|
||||
return nil
|
||||
}
|
||||
var amount32 [32]byte
|
||||
copy(amount32[32-len(bigamount):32], bigamount)
|
||||
input := append(contract.Bytes(), beneficiary.Bytes()...)
|
||||
input = append(input, amount32[:]...)
|
||||
return crypto.Keccak256(input)
|
||||
}
|
||||
|
||||
// Balance returns the current balance of the chequebook.
|
||||
func (cb *Chequebook) Balance() *big.Int {
|
||||
defer cb.lock.Unlock()
|
||||
cb.lock.Lock()
|
||||
return new(big.Int).Set(cb.balance)
|
||||
}
|
||||
|
||||
// Owner returns the owner account of the chequebook.
|
||||
func (cb *Chequebook) Owner() common.Address {
|
||||
return cb.owner
|
||||
}
|
||||
|
||||
// Address returns the on-chain contract address of the chequebook.
|
||||
func (cb *Chequebook) Address() common.Address {
|
||||
return cb.contractAddr
|
||||
}
|
||||
|
||||
// Deposit deposits money to the chequebook account.
|
||||
func (cb *Chequebook) Deposit(amount *big.Int) (string, error) {
|
||||
defer cb.lock.Unlock()
|
||||
cb.lock.Lock()
|
||||
return cb.deposit(amount)
|
||||
}
|
||||
|
||||
// deposit deposits amount to the chequebook account.
|
||||
// The caller must hold lock.
|
||||
func (cb *Chequebook) deposit(amount *big.Int) (string, error) {
|
||||
// since the amount is variable here, we do not use sessions
|
||||
depositTransactor := bind.NewKeyedTransactor(cb.prvKey)
|
||||
depositTransactor.Value = amount
|
||||
chbookRaw := &contract.ChequebookRaw{Contract: cb.contract}
|
||||
tx, err := chbookRaw.Transfer(depositTransactor)
|
||||
if err != nil {
|
||||
cb.log.Warn("Failed to fund chequebook", "amount", amount, "balance", cb.balance, "target", cb.buffer, "err", err)
|
||||
return "", err
|
||||
}
|
||||
// assume that transaction is actually successful, we add the amount to balance right away
|
||||
cb.balance.Add(cb.balance, amount)
|
||||
cb.log.Trace("Deposited funds to chequebook", "amount", amount, "balance", cb.balance, "target", cb.buffer)
|
||||
return tx.Hash().Hex(), nil
|
||||
}
|
||||
|
||||
// AutoDeposit (re)sets interval time and amount which triggers sending funds to the
|
||||
// chequebook. Contract backend needs to be set if threshold is not less than buffer, then
|
||||
// deposit will be triggered on every new cheque issued.
|
||||
func (cb *Chequebook) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) {
|
||||
defer cb.lock.Unlock()
|
||||
cb.lock.Lock()
|
||||
cb.threshold = threshold
|
||||
cb.buffer = buffer
|
||||
cb.autoDeposit(interval)
|
||||
}
|
||||
|
||||
// autoDeposit starts a goroutine that periodically sends funds to the chequebook
|
||||
// contract caller holds the lock the go routine terminates if Chequebook.quit is closed.
|
||||
func (cb *Chequebook) autoDeposit(interval time.Duration) {
|
||||
if cb.quit != nil {
|
||||
close(cb.quit)
|
||||
cb.quit = nil
|
||||
}
|
||||
// if threshold >= balance autodeposit after every cheque issued
|
||||
if interval == time.Duration(0) || cb.threshold != nil && cb.buffer != nil && cb.threshold.Cmp(cb.buffer) >= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
cb.quit = make(chan bool)
|
||||
quit := cb.quit
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
case <-ticker.C:
|
||||
cb.lock.Lock()
|
||||
if cb.balance.Cmp(cb.buffer) < 0 {
|
||||
amount := new(big.Int).Sub(cb.buffer, cb.balance)
|
||||
txhash, err := cb.deposit(amount)
|
||||
if err == nil {
|
||||
cb.txhash = txhash
|
||||
}
|
||||
}
|
||||
cb.lock.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Outbox can issue cheques from a single contract to a single beneficiary.
|
||||
type Outbox struct {
|
||||
chequeBook *Chequebook
|
||||
beneficiary common.Address
|
||||
}
|
||||
|
||||
// NewOutbox creates an outbox.
|
||||
func NewOutbox(cb *Chequebook, beneficiary common.Address) *Outbox {
|
||||
return &Outbox{cb, beneficiary}
|
||||
}
|
||||
|
||||
// Issue creates cheque.
|
||||
func (o *Outbox) Issue(amount *big.Int) (swap.Promise, error) {
|
||||
return o.chequeBook.Issue(o.beneficiary, amount)
|
||||
}
|
||||
|
||||
// AutoDeposit enables auto-deposits on the underlying chequebook.
|
||||
func (o *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) {
|
||||
o.chequeBook.AutoDeposit(interval, threshold, buffer)
|
||||
}
|
||||
|
||||
// Stop helps satisfy the swap.OutPayment interface.
|
||||
func (o *Outbox) Stop() {}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (o *Outbox) String() string {
|
||||
return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", o.chequeBook.Address().Hex(), o.beneficiary.Hex(), o.chequeBook.Balance())
|
||||
}
|
||||
|
||||
// Inbox can deposit, verify and cash cheques from a single contract to a single
|
||||
// beneficiary. It is the incoming payment handler for peer to peer micropayments.
|
||||
type Inbox struct {
|
||||
lock sync.Mutex
|
||||
contract common.Address // peer's chequebook contract
|
||||
beneficiary common.Address // local peer's receiving address
|
||||
sender common.Address // local peer's address to send cashing tx from
|
||||
signer *ecdsa.PublicKey // peer's public key
|
||||
txhash string // tx hash of last cashing tx
|
||||
session *contract.ChequebookSession // abi contract backend with tx opts
|
||||
quit chan bool // when closed causes autocash to stop
|
||||
maxUncashed *big.Int // threshold that triggers autocashing
|
||||
cashed *big.Int // cumulative amount cashed
|
||||
cheque *Cheque // last cheque, nil if none yet received
|
||||
log log.Logger // contextual logger with the contract address embedded
|
||||
}
|
||||
|
||||
// NewInbox creates an Inbox. An Inboxes is not persisted, the cumulative sum is updated
|
||||
// from blockchain when first cheque is received.
|
||||
func NewInbox(prvKey *ecdsa.PrivateKey, contractAddr, beneficiary common.Address, signer *ecdsa.PublicKey, abigen bind.ContractBackend) (*Inbox, error) {
|
||||
if signer == nil {
|
||||
return nil, fmt.Errorf("signer is null")
|
||||
}
|
||||
chbook, err := contract.NewChequebook(contractAddr, abigen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transactOpts := bind.NewKeyedTransactor(prvKey)
|
||||
transactOpts.GasLimit = gasToCash
|
||||
session := &contract.ChequebookSession{
|
||||
Contract: chbook,
|
||||
TransactOpts: *transactOpts,
|
||||
}
|
||||
sender := transactOpts.From
|
||||
|
||||
inbox := &Inbox{
|
||||
contract: contractAddr,
|
||||
beneficiary: beneficiary,
|
||||
sender: sender,
|
||||
signer: signer,
|
||||
session: session,
|
||||
cashed: new(big.Int).Set(common.Big0),
|
||||
log: log.New("contract", contractAddr),
|
||||
}
|
||||
inbox.log.Trace("New chequebook inbox initialized", "beneficiary", inbox.beneficiary, "signer", hexutil.Bytes(crypto.FromECDSAPub(signer)))
|
||||
return inbox, nil
|
||||
}
|
||||
|
||||
func (i *Inbox) String() string {
|
||||
return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", i.contract.Hex(), i.beneficiary.Hex(), i.cheque.Amount)
|
||||
}
|
||||
|
||||
// Stop quits the autocash goroutine.
|
||||
func (i *Inbox) Stop() {
|
||||
defer i.lock.Unlock()
|
||||
i.lock.Lock()
|
||||
if i.quit != nil {
|
||||
close(i.quit)
|
||||
i.quit = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Cash attempts to cash the current cheque.
|
||||
func (i *Inbox) Cash() (string, error) {
|
||||
if i.cheque == nil {
|
||||
return "", nil
|
||||
}
|
||||
txhash, err := i.cheque.Cash(i.session)
|
||||
i.log.Trace("Cashing in chequebook cheque", "amount", i.cheque.Amount, "beneficiary", i.beneficiary)
|
||||
i.cashed = i.cheque.Amount
|
||||
|
||||
return txhash, err
|
||||
}
|
||||
|
||||
// AutoCash (re)sets maximum time and amount which triggers cashing of the last uncashed
|
||||
// cheque if maxUncashed is set to 0, then autocash on receipt.
|
||||
func (i *Inbox) AutoCash(cashInterval time.Duration, maxUncashed *big.Int) {
|
||||
defer i.lock.Unlock()
|
||||
i.lock.Lock()
|
||||
i.maxUncashed = maxUncashed
|
||||
i.autoCash(cashInterval)
|
||||
}
|
||||
|
||||
// autoCash starts a loop that periodically clears the last cheque
|
||||
// if the peer is trusted. Clearing period could be 24h or a week.
|
||||
// The caller must hold lock.
|
||||
func (i *Inbox) autoCash(cashInterval time.Duration) {
|
||||
if i.quit != nil {
|
||||
close(i.quit)
|
||||
i.quit = nil
|
||||
}
|
||||
// if maxUncashed is set to 0, then autocash on receipt
|
||||
if cashInterval == time.Duration(0) || i.maxUncashed != nil && i.maxUncashed.Sign() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(cashInterval)
|
||||
i.quit = make(chan bool)
|
||||
quit := i.quit
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-quit:
|
||||
return
|
||||
case <-ticker.C:
|
||||
i.lock.Lock()
|
||||
if i.cheque != nil && i.cheque.Amount.Cmp(i.cashed) != 0 {
|
||||
txhash, err := i.Cash()
|
||||
if err == nil {
|
||||
i.txhash = txhash
|
||||
}
|
||||
}
|
||||
i.lock.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Receive is called to deposit the latest cheque to the incoming Inbox.
|
||||
// The given promise must be a *Cheque.
|
||||
func (i *Inbox) Receive(promise swap.Promise) (*big.Int, error) {
|
||||
ch := promise.(*Cheque)
|
||||
|
||||
defer i.lock.Unlock()
|
||||
i.lock.Lock()
|
||||
|
||||
var sum *big.Int
|
||||
if i.cheque == nil {
|
||||
// the sum is checked against the blockchain once a cheque is received
|
||||
tally, err := i.session.Sent(i.beneficiary)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("inbox: error calling backend to set amount: %v", err)
|
||||
}
|
||||
sum = tally
|
||||
} else {
|
||||
sum = i.cheque.Amount
|
||||
}
|
||||
|
||||
amount, err := ch.Verify(i.signer, i.contract, i.beneficiary, sum)
|
||||
var uncashed *big.Int
|
||||
if err == nil {
|
||||
i.cheque = ch
|
||||
|
||||
if i.maxUncashed != nil {
|
||||
uncashed = new(big.Int).Sub(ch.Amount, i.cashed)
|
||||
if i.maxUncashed.Cmp(uncashed) < 0 {
|
||||
i.Cash()
|
||||
}
|
||||
}
|
||||
i.log.Trace("Received cheque in chequebook inbox", "amount", amount, "uncashed", uncashed)
|
||||
}
|
||||
|
||||
return amount, err
|
||||
}
|
||||
|
||||
// Verify verifies cheque for signer, contract, beneficiary, amount, valid signature.
|
||||
func (ch *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) {
|
||||
log.Trace("Verifying chequebook cheque", "cheque", ch, "sum", sum)
|
||||
if sum == nil {
|
||||
return nil, fmt.Errorf("invalid amount")
|
||||
}
|
||||
|
||||
if ch.Beneficiary != beneficiary {
|
||||
return nil, fmt.Errorf("beneficiary mismatch: %v != %v", ch.Beneficiary.Hex(), beneficiary.Hex())
|
||||
}
|
||||
if ch.Contract != contract {
|
||||
return nil, fmt.Errorf("contract mismatch: %v != %v", ch.Contract.Hex(), contract.Hex())
|
||||
}
|
||||
|
||||
amount := new(big.Int).Set(ch.Amount)
|
||||
if sum != nil {
|
||||
amount.Sub(amount, sum)
|
||||
if amount.Sign() <= 0 {
|
||||
return nil, fmt.Errorf("incorrect amount: %v <= 0", amount)
|
||||
}
|
||||
}
|
||||
|
||||
pubKey, err := crypto.SigToPub(sigHash(ch.Contract, beneficiary, ch.Amount), ch.Sig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid signature: %v", err)
|
||||
}
|
||||
if !bytes.Equal(crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) {
|
||||
return nil, fmt.Errorf("signer mismatch: %x != %x", crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey))
|
||||
}
|
||||
return amount, nil
|
||||
}
|
||||
|
||||
// v/r/s representation of signature
|
||||
func sig2vrs(sig []byte) (v byte, r, s [32]byte) {
|
||||
v = sig[64] + 27
|
||||
copy(r[:], sig[:32])
|
||||
copy(s[:], sig[32:64])
|
||||
return
|
||||
}
|
||||
|
||||
// Cash cashes the cheque by sending an Ethereum transaction.
|
||||
func (ch *Cheque) Cash(session *contract.ChequebookSession) (string, error) {
|
||||
v, r, s := sig2vrs(ch.Sig)
|
||||
tx, err := session.Cash(ch.Beneficiary, ch.Amount, v, r, s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tx.Hash().Hex(), nil
|
||||
}
|
||||
|
||||
// ValidateCode checks that the on-chain code at address matches the expected chequebook
|
||||
// contract code. This is used to detect suicided chequebooks.
|
||||
func ValidateCode(ctx context.Context, b Backend, address common.Address) (bool, error) {
|
||||
code, err := b.CodeAt(ctx, address, nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return bytes.Equal(code, common.FromHex(contract.ContractDeployedCode)), nil
|
||||
}
|
||||
|
|
@ -1,487 +0,0 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package chequebook
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/contracts/chequebook/contract"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
var (
|
||||
key0, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
key1, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
key2, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
addr0 = crypto.PubkeyToAddress(key0.PublicKey)
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
addr2 = crypto.PubkeyToAddress(key2.PublicKey)
|
||||
)
|
||||
|
||||
func newTestBackend() *backends.SimulatedBackend {
|
||||
return backends.NewSimulatedBackend(core.GenesisAlloc{
|
||||
addr0: {Balance: big.NewInt(1000000000)},
|
||||
addr1: {Balance: big.NewInt(1000000000)},
|
||||
addr2: {Balance: big.NewInt(1000000000)},
|
||||
}, 10000000)
|
||||
}
|
||||
|
||||
func deploy(prvKey *ecdsa.PrivateKey, amount *big.Int, backend *backends.SimulatedBackend) (common.Address, error) {
|
||||
deployTransactor := bind.NewKeyedTransactor(prvKey)
|
||||
deployTransactor.Value = amount
|
||||
addr, _, _, err := contract.DeployChequebook(deployTransactor, backend)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
backend.Commit()
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
func TestIssueAndReceive(t *testing.T) {
|
||||
path := filepath.Join(os.TempDir(), "chequebook-test.json")
|
||||
backend := newTestBackend()
|
||||
addr0, err := deploy(key0, big.NewInt(0), backend)
|
||||
if err != nil {
|
||||
t.Fatalf("deploy contract: expected no error, got %v", err)
|
||||
}
|
||||
chbook, err := NewChequebook(path, addr0, key0, backend)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
chbook.sent[addr1] = new(big.Int).SetUint64(42)
|
||||
amount := common.Big1
|
||||
|
||||
if _, err = chbook.Issue(addr1, amount); err == nil {
|
||||
t.Fatalf("expected insufficient funds error, got none")
|
||||
}
|
||||
|
||||
chbook.balance = new(big.Int).Set(common.Big1)
|
||||
if chbook.Balance().Cmp(common.Big1) != 0 {
|
||||
t.Fatalf("expected: %v, got %v", "0", chbook.Balance())
|
||||
}
|
||||
|
||||
ch, err := chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if chbook.Balance().Sign() != 0 {
|
||||
t.Errorf("expected: %v, got %v", "0", chbook.Balance())
|
||||
}
|
||||
|
||||
chbox, err := NewInbox(key1, addr0, addr1, &key0.PublicKey, backend)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
received, err := chbox.Receive(ch)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if received.Cmp(big.NewInt(43)) != 0 {
|
||||
t.Errorf("expected: %v, got %v", "43", received)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCheckbookFile(t *testing.T) {
|
||||
path := filepath.Join(os.TempDir(), "chequebook-test.json")
|
||||
backend := newTestBackend()
|
||||
chbook, err := NewChequebook(path, addr0, key0, backend)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
chbook.sent[addr1] = new(big.Int).SetUint64(42)
|
||||
chbook.balance = new(big.Int).Set(common.Big1)
|
||||
|
||||
chbook.Save()
|
||||
|
||||
chbook, err = LoadChequebook(path, key0, backend, false)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if chbook.Balance().Cmp(common.Big1) != 0 {
|
||||
t.Errorf("expected: %v, got %v", "0", chbook.Balance())
|
||||
}
|
||||
|
||||
var ch *Cheque
|
||||
if ch, err = chbook.Issue(addr1, common.Big1); err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if ch.Amount.Cmp(new(big.Int).SetUint64(43)) != 0 {
|
||||
t.Errorf("expected: %v, got %v", "0", ch.Amount)
|
||||
}
|
||||
|
||||
err = chbook.Save()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyErrors(t *testing.T) {
|
||||
path0 := filepath.Join(os.TempDir(), "chequebook-test-0.json")
|
||||
backend := newTestBackend()
|
||||
contr0, err := deploy(key0, common.Big2, backend)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
chbook0, err := NewChequebook(path0, contr0, key0, backend)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
path1 := filepath.Join(os.TempDir(), "chequebook-test-1.json")
|
||||
contr1, _ := deploy(key1, common.Big2, backend)
|
||||
chbook1, err := NewChequebook(path1, contr1, key1, backend)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
chbook0.sent[addr1] = new(big.Int).SetUint64(42)
|
||||
chbook0.balance = new(big.Int).Set(common.Big2)
|
||||
chbook1.balance = new(big.Int).Set(common.Big1)
|
||||
amount := common.Big1
|
||||
ch0, err := chbook0.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
received, err := chbox.Receive(ch0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
if received.Cmp(big.NewInt(43)) != 0 {
|
||||
t.Errorf("expected: %v, got %v", "43", received)
|
||||
}
|
||||
|
||||
ch1, err := chbook0.Issue(addr2, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
received, err = chbox.Receive(ch1)
|
||||
t.Logf("correct error: %v", err)
|
||||
if err == nil {
|
||||
t.Fatalf("expected receiver error, got none and value %v", received)
|
||||
}
|
||||
|
||||
ch2, err := chbook1.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
received, err = chbox.Receive(ch2)
|
||||
t.Logf("correct error: %v", err)
|
||||
if err == nil {
|
||||
t.Fatalf("expected sender error, got none and value %v", received)
|
||||
}
|
||||
|
||||
_, err = chbook1.Issue(addr1, new(big.Int).SetInt64(-1))
|
||||
t.Logf("correct error: %v", err)
|
||||
if err == nil {
|
||||
t.Fatalf("expected incorrect amount error, got none")
|
||||
}
|
||||
|
||||
received, err = chbox.Receive(ch0)
|
||||
t.Logf("correct error: %v", err)
|
||||
if err == nil {
|
||||
t.Fatalf("expected incorrect amount error, got none and value %v", received)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDeposit(t *testing.T) {
|
||||
path0 := filepath.Join(os.TempDir(), "chequebook-test-0.json")
|
||||
backend := newTestBackend()
|
||||
contr0, _ := deploy(key0, new(big.Int), backend)
|
||||
|
||||
chbook, err := NewChequebook(path0, contr0, key0, backend)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
balance := new(big.Int).SetUint64(42)
|
||||
chbook.Deposit(balance)
|
||||
backend.Commit()
|
||||
if chbook.Balance().Cmp(balance) != 0 {
|
||||
t.Fatalf("expected balance %v, got %v", balance, chbook.Balance())
|
||||
}
|
||||
|
||||
amount := common.Big1
|
||||
_, err = chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
exp := new(big.Int).SetUint64(41)
|
||||
if chbook.Balance().Cmp(exp) != 0 {
|
||||
t.Fatalf("expected balance %v, got %v", exp, chbook.Balance())
|
||||
}
|
||||
|
||||
// autodeposit on each issue
|
||||
chbook.AutoDeposit(0, balance, balance)
|
||||
_, err = chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
_, err = chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
if chbook.Balance().Cmp(balance) != 0 {
|
||||
t.Fatalf("expected balance %v, got %v", balance, chbook.Balance())
|
||||
}
|
||||
|
||||
// autodeposit off
|
||||
chbook.AutoDeposit(0, common.Big0, balance)
|
||||
_, err = chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
_, err = chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
|
||||
exp = new(big.Int).SetUint64(40)
|
||||
if chbook.Balance().Cmp(exp) != 0 {
|
||||
t.Fatalf("expected balance %v, got %v", exp, chbook.Balance())
|
||||
}
|
||||
|
||||
// autodeposit every 200ms if new cheque issued
|
||||
interval := 200 * time.Millisecond
|
||||
chbook.AutoDeposit(interval, common.Big1, balance)
|
||||
_, err = chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
_, err = chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
|
||||
exp = new(big.Int).SetUint64(38)
|
||||
if chbook.Balance().Cmp(exp) != 0 {
|
||||
t.Fatalf("expected balance %v, got %v", exp, chbook.Balance())
|
||||
}
|
||||
|
||||
time.Sleep(3 * interval)
|
||||
backend.Commit()
|
||||
if chbook.Balance().Cmp(balance) != 0 {
|
||||
t.Fatalf("expected balance %v, got %v", balance, chbook.Balance())
|
||||
}
|
||||
|
||||
exp = new(big.Int).SetUint64(40)
|
||||
chbook.AutoDeposit(4*interval, exp, balance)
|
||||
_, err = chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
_, err = chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
time.Sleep(3 * interval)
|
||||
backend.Commit()
|
||||
if chbook.Balance().Cmp(exp) != 0 {
|
||||
t.Fatalf("expected balance %v, got %v", exp, chbook.Balance())
|
||||
}
|
||||
|
||||
_, err = chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
time.Sleep(1 * interval)
|
||||
backend.Commit()
|
||||
|
||||
if chbook.Balance().Cmp(balance) != 0 {
|
||||
t.Fatalf("expected balance %v, got %v", balance, chbook.Balance())
|
||||
}
|
||||
|
||||
chbook.AutoDeposit(1*interval, common.Big0, balance)
|
||||
chbook.Stop()
|
||||
|
||||
_, err = chbook.Issue(addr1, common.Big1)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
|
||||
_, err = chbook.Issue(addr1, common.Big2)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(1 * interval)
|
||||
backend.Commit()
|
||||
|
||||
exp = new(big.Int).SetUint64(39)
|
||||
if chbook.Balance().Cmp(exp) != 0 {
|
||||
t.Fatalf("expected balance %v, got %v", exp, chbook.Balance())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCash(t *testing.T) {
|
||||
path := filepath.Join(os.TempDir(), "chequebook-test.json")
|
||||
backend := newTestBackend()
|
||||
contr0, _ := deploy(key0, common.Big2, backend)
|
||||
|
||||
chbook, err := NewChequebook(path, contr0, key0, backend)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
chbook.sent[addr1] = new(big.Int).SetUint64(42)
|
||||
amount := common.Big1
|
||||
chbook.balance = new(big.Int).Set(common.Big1)
|
||||
ch, err := chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
// cashing latest cheque
|
||||
if _, err = chbox.Receive(ch); err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
if _, err = ch.Cash(chbook.session); err != nil {
|
||||
t.Fatal("Cash failed:", err)
|
||||
}
|
||||
backend.Commit()
|
||||
|
||||
chbook.balance = new(big.Int).Set(common.Big3)
|
||||
ch0, err := chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
ch1, err := chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
|
||||
interval := 10 * time.Millisecond
|
||||
// setting autocash with interval of 10ms
|
||||
chbox.AutoCash(interval, nil)
|
||||
_, err = chbox.Receive(ch0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
_, err = chbox.Receive(ch1)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
// after 3x interval time and 2 cheques received, exactly one cashing tx is sent
|
||||
time.Sleep(4 * interval)
|
||||
backend.Commit()
|
||||
|
||||
// after stopping autocash no more tx are sent
|
||||
ch2, err := chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
chbox.Stop()
|
||||
_, err = chbox.Receive(ch2)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
time.Sleep(2 * interval)
|
||||
backend.Commit()
|
||||
|
||||
// autocash below 1
|
||||
chbook.balance = big.NewInt(2)
|
||||
chbox.AutoCash(0, common.Big1)
|
||||
|
||||
ch3, err := chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
|
||||
ch4, err := chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
|
||||
_, err = chbox.Receive(ch3)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
_, err = chbox.Receive(ch4)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
|
||||
// autochash on receipt when maxUncashed is 0
|
||||
chbook.balance = new(big.Int).Set(common.Big2)
|
||||
chbox.AutoCash(0, common.Big0)
|
||||
|
||||
ch5, err := chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
|
||||
ch6, err := chbook.Issue(addr1, amount)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
_, err = chbox.Receive(ch5)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
|
||||
_, err = chbox.Receive(ch6)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error, got %v", err)
|
||||
}
|
||||
backend.Commit()
|
||||
|
||||
}
|
||||
|
|
@ -1,367 +0,0 @@
|
|||
// Code generated - DO NOT EDIT.
|
||||
// This file is a generated binding and any manual changes will be lost.
|
||||
|
||||
package contract
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
ethereum "github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
)
|
||||
|
||||
// ChequebookABI is the input ABI used to generate the binding from.
|
||||
const ChequebookABI = "[{\"constant\":false,\"inputs\":[],\"name\":\"kill\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"sent\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"beneficiary\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"},{\"name\":\"sig_v\",\"type\":\"uint8\"},{\"name\":\"sig_r\",\"type\":\"bytes32\"},{\"name\":\"sig_s\",\"type\":\"bytes32\"}],\"name\":\"cash\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"deadbeat\",\"type\":\"address\"}],\"name\":\"Overdraft\",\"type\":\"event\"}]"
|
||||
|
||||
// ChequebookBin is the compiled bytecode used for deploying new contracts.
|
||||
const ChequebookBin = `0x606060405260008054600160a060020a033316600160a060020a03199091161790556102ec806100306000396000f3006060604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b581146100585780637bf786f81461006b578063fbf788d61461009c575b005b341561006357600080fd5b6100566100ca565b341561007657600080fd5b61008a600160a060020a03600435166100f1565b60405190815260200160405180910390f35b34156100a757600080fd5b610056600160a060020a036004351660243560ff60443516606435608435610103565b60005433600160a060020a03908116911614156100ef57600054600160a060020a0316ff5b565b60016020526000908152604090205481565b600160a060020a0385166000908152600160205260408120548190861161012957600080fd5b3087876040516c01000000000000000000000000600160a060020a03948516810282529290931690910260148301526028820152604801604051809103902091506001828686866040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f115156101cf57600080fd5b505060206040510351600054600160a060020a039081169116146101f257600080fd5b50600160a060020a03808716600090815260016020526040902054860390301631811161026257600160a060020a0387166000818152600160205260409081902088905582156108fc0290839051600060405180830381858888f19350505050151561025d57600080fd5b6102b7565b6000547f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f97890600160a060020a0316604051600160a060020a03909116815260200160405180910390a186600160a060020a0316ff5b505050505050505600a165627a7a72305820533e856fc37e3d64d1706bcc7dfb6b1d490c8d566ea498d9d01ec08965a896ca0029`
|
||||
|
||||
// DeployChequebook deploys a new Ethereum contract, binding an instance of Chequebook to it.
|
||||
func DeployChequebook(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Chequebook, error) {
|
||||
parsed, err := abi.JSON(strings.NewReader(ChequebookABI))
|
||||
if err != nil {
|
||||
return common.Address{}, nil, nil, err
|
||||
}
|
||||
address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ChequebookBin), backend)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, nil, err
|
||||
}
|
||||
return address, tx, &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}, ChequebookFilterer: ChequebookFilterer{contract: contract}}, nil
|
||||
}
|
||||
|
||||
// Chequebook is an auto generated Go binding around an Ethereum contract.
|
||||
type Chequebook struct {
|
||||
ChequebookCaller // Read-only binding to the contract
|
||||
ChequebookTransactor // Write-only binding to the contract
|
||||
ChequebookFilterer // Log filterer for contract events
|
||||
}
|
||||
|
||||
// ChequebookCaller is an auto generated read-only Go binding around an Ethereum contract.
|
||||
type ChequebookCaller struct {
|
||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||
}
|
||||
|
||||
// ChequebookTransactor is an auto generated write-only Go binding around an Ethereum contract.
|
||||
type ChequebookTransactor struct {
|
||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||
}
|
||||
|
||||
// ChequebookFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
|
||||
type ChequebookFilterer struct {
|
||||
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||
}
|
||||
|
||||
// ChequebookSession is an auto generated Go binding around an Ethereum contract,
|
||||
// with pre-set call and transact options.
|
||||
type ChequebookSession struct {
|
||||
Contract *Chequebook // Generic contract binding to set the session for
|
||||
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||
}
|
||||
|
||||
// ChequebookCallerSession is an auto generated read-only Go binding around an Ethereum contract,
|
||||
// with pre-set call options.
|
||||
type ChequebookCallerSession struct {
|
||||
Contract *ChequebookCaller // Generic contract caller binding to set the session for
|
||||
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||
}
|
||||
|
||||
// ChequebookTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
|
||||
// with pre-set transact options.
|
||||
type ChequebookTransactorSession struct {
|
||||
Contract *ChequebookTransactor // Generic contract transactor binding to set the session for
|
||||
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||
}
|
||||
|
||||
// ChequebookRaw is an auto generated low-level Go binding around an Ethereum contract.
|
||||
type ChequebookRaw struct {
|
||||
Contract *Chequebook // Generic contract binding to access the raw methods on
|
||||
}
|
||||
|
||||
// ChequebookCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
|
||||
type ChequebookCallerRaw struct {
|
||||
Contract *ChequebookCaller // Generic read-only contract binding to access the raw methods on
|
||||
}
|
||||
|
||||
// ChequebookTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
|
||||
type ChequebookTransactorRaw struct {
|
||||
Contract *ChequebookTransactor // Generic write-only contract binding to access the raw methods on
|
||||
}
|
||||
|
||||
// NewChequebook creates a new instance of Chequebook, bound to a specific deployed contract.
|
||||
func NewChequebook(address common.Address, backend bind.ContractBackend) (*Chequebook, error) {
|
||||
contract, err := bindChequebook(address, backend, backend, backend)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}, ChequebookFilterer: ChequebookFilterer{contract: contract}}, nil
|
||||
}
|
||||
|
||||
// NewChequebookCaller creates a new read-only instance of Chequebook, bound to a specific deployed contract.
|
||||
func NewChequebookCaller(address common.Address, caller bind.ContractCaller) (*ChequebookCaller, error) {
|
||||
contract, err := bindChequebook(address, caller, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ChequebookCaller{contract: contract}, nil
|
||||
}
|
||||
|
||||
// NewChequebookTransactor creates a new write-only instance of Chequebook, bound to a specific deployed contract.
|
||||
func NewChequebookTransactor(address common.Address, transactor bind.ContractTransactor) (*ChequebookTransactor, error) {
|
||||
contract, err := bindChequebook(address, nil, transactor, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ChequebookTransactor{contract: contract}, nil
|
||||
}
|
||||
|
||||
// NewChequebookFilterer creates a new log filterer instance of Chequebook, bound to a specific deployed contract.
|
||||
func NewChequebookFilterer(address common.Address, filterer bind.ContractFilterer) (*ChequebookFilterer, error) {
|
||||
contract, err := bindChequebook(address, nil, nil, filterer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ChequebookFilterer{contract: contract}, nil
|
||||
}
|
||||
|
||||
// bindChequebook binds a generic wrapper to an already deployed contract.
|
||||
func bindChequebook(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
|
||||
parsed, err := abi.JSON(strings.NewReader(ChequebookABI))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
|
||||
}
|
||||
|
||||
// Call invokes the (constant) contract method with params as input values and
|
||||
// sets the output to result. The result type might be a single field for simple
|
||||
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||
// returns.
|
||||
func (_Chequebook *ChequebookRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
|
||||
return _Chequebook.Contract.ChequebookCaller.contract.Call(opts, result, method, params...)
|
||||
}
|
||||
|
||||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||
// its default method if one is available.
|
||||
func (_Chequebook *ChequebookRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||
return _Chequebook.Contract.ChequebookTransactor.contract.Transfer(opts)
|
||||
}
|
||||
|
||||
// Transact invokes the (paid) contract method with params as input values.
|
||||
func (_Chequebook *ChequebookRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||
return _Chequebook.Contract.ChequebookTransactor.contract.Transact(opts, method, params...)
|
||||
}
|
||||
|
||||
// Call invokes the (constant) contract method with params as input values and
|
||||
// sets the output to result. The result type might be a single field for simple
|
||||
// returns, a slice of interfaces for anonymous returns and a struct for named
|
||||
// returns.
|
||||
func (_Chequebook *ChequebookCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
|
||||
return _Chequebook.Contract.contract.Call(opts, result, method, params...)
|
||||
}
|
||||
|
||||
// Transfer initiates a plain transaction to move funds to the contract, calling
|
||||
// its default method if one is available.
|
||||
func (_Chequebook *ChequebookTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||
return _Chequebook.Contract.contract.Transfer(opts)
|
||||
}
|
||||
|
||||
// Transact invokes the (paid) contract method with params as input values.
|
||||
func (_Chequebook *ChequebookTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||
return _Chequebook.Contract.contract.Transact(opts, method, params...)
|
||||
}
|
||||
|
||||
// Sent is a free data retrieval call binding the contract method 0x7bf786f8.
|
||||
//
|
||||
// Solidity: function sent( address) constant returns(uint256)
|
||||
func (_Chequebook *ChequebookCaller) Sent(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) {
|
||||
var (
|
||||
ret0 = new(*big.Int)
|
||||
)
|
||||
out := ret0
|
||||
err := _Chequebook.contract.Call(opts, out, "sent", arg0)
|
||||
return *ret0, err
|
||||
}
|
||||
|
||||
// Sent is a free data retrieval call binding the contract method 0x7bf786f8.
|
||||
//
|
||||
// Solidity: function sent( address) constant returns(uint256)
|
||||
func (_Chequebook *ChequebookSession) Sent(arg0 common.Address) (*big.Int, error) {
|
||||
return _Chequebook.Contract.Sent(&_Chequebook.CallOpts, arg0)
|
||||
}
|
||||
|
||||
// Sent is a free data retrieval call binding the contract method 0x7bf786f8.
|
||||
//
|
||||
// Solidity: function sent( address) constant returns(uint256)
|
||||
func (_Chequebook *ChequebookCallerSession) Sent(arg0 common.Address) (*big.Int, error) {
|
||||
return _Chequebook.Contract.Sent(&_Chequebook.CallOpts, arg0)
|
||||
}
|
||||
|
||||
// Cash is a paid mutator transaction binding the contract method 0xfbf788d6.
|
||||
//
|
||||
// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns()
|
||||
func (_Chequebook *ChequebookTransactor) Cash(opts *bind.TransactOpts, beneficiary common.Address, amount *big.Int, sigV uint8, sigR [32]byte, sigS [32]byte) (*types.Transaction, error) {
|
||||
return _Chequebook.contract.Transact(opts, "cash", beneficiary, amount, sigV, sigR, sigS)
|
||||
}
|
||||
|
||||
// Cash is a paid mutator transaction binding the contract method 0xfbf788d6.
|
||||
//
|
||||
// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns()
|
||||
func (_Chequebook *ChequebookSession) Cash(beneficiary common.Address, amount *big.Int, sigV uint8, sigR [32]byte, sigS [32]byte) (*types.Transaction, error) {
|
||||
return _Chequebook.Contract.Cash(&_Chequebook.TransactOpts, beneficiary, amount, sigV, sigR, sigS)
|
||||
}
|
||||
|
||||
// Cash is a paid mutator transaction binding the contract method 0xfbf788d6.
|
||||
//
|
||||
// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns()
|
||||
func (_Chequebook *ChequebookTransactorSession) Cash(beneficiary common.Address, amount *big.Int, sigV uint8, sigR [32]byte, sigS [32]byte) (*types.Transaction, error) {
|
||||
return _Chequebook.Contract.Cash(&_Chequebook.TransactOpts, beneficiary, amount, sigV, sigR, sigS)
|
||||
}
|
||||
|
||||
// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5.
|
||||
//
|
||||
// Solidity: function kill() returns()
|
||||
func (_Chequebook *ChequebookTransactor) Kill(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||
return _Chequebook.contract.Transact(opts, "kill")
|
||||
}
|
||||
|
||||
// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5.
|
||||
//
|
||||
// Solidity: function kill() returns()
|
||||
func (_Chequebook *ChequebookSession) Kill() (*types.Transaction, error) {
|
||||
return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts)
|
||||
}
|
||||
|
||||
// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5.
|
||||
//
|
||||
// Solidity: function kill() returns()
|
||||
func (_Chequebook *ChequebookTransactorSession) Kill() (*types.Transaction, error) {
|
||||
return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts)
|
||||
}
|
||||
|
||||
// ChequebookOverdraftIterator is returned from FilterOverdraft and is used to iterate over the raw logs and unpacked data for Overdraft events raised by the Chequebook contract.
|
||||
type ChequebookOverdraftIterator struct {
|
||||
Event *ChequebookOverdraft // Event containing the contract specifics and raw log
|
||||
|
||||
contract *bind.BoundContract // Generic contract to use for unpacking event data
|
||||
event string // Event name to use for unpacking event data
|
||||
|
||||
logs chan types.Log // Log channel receiving the found contract events
|
||||
sub ethereum.Subscription // Subscription for errors, completion and termination
|
||||
done bool // Whether the subscription completed delivering logs
|
||||
fail error // Occurred error to stop iteration
|
||||
}
|
||||
|
||||
// Next advances the iterator to the subsequent event, returning whether there
|
||||
// are any more events found. In case of a retrieval or parsing error, false is
|
||||
// returned and Error() can be queried for the exact failure.
|
||||
func (it *ChequebookOverdraftIterator) Next() bool {
|
||||
// If the iterator failed, stop iterating
|
||||
if it.fail != nil {
|
||||
return false
|
||||
}
|
||||
// If the iterator completed, deliver directly whatever's available
|
||||
if it.done {
|
||||
select {
|
||||
case log := <-it.logs:
|
||||
it.Event = new(ChequebookOverdraft)
|
||||
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
||||
it.fail = err
|
||||
return false
|
||||
}
|
||||
it.Event.Raw = log
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Iterator still in progress, wait for either a data or an error event
|
||||
select {
|
||||
case log := <-it.logs:
|
||||
it.Event = new(ChequebookOverdraft)
|
||||
if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
|
||||
it.fail = err
|
||||
return false
|
||||
}
|
||||
it.Event.Raw = log
|
||||
return true
|
||||
|
||||
case err := <-it.sub.Err():
|
||||
it.done = true
|
||||
it.fail = err
|
||||
return it.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// Error retruned any retrieval or parsing error occurred during filtering.
|
||||
func (it *ChequebookOverdraftIterator) Error() error {
|
||||
return it.fail
|
||||
}
|
||||
|
||||
// Close terminates the iteration process, releasing any pending underlying
|
||||
// resources.
|
||||
func (it *ChequebookOverdraftIterator) Close() error {
|
||||
it.sub.Unsubscribe()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChequebookOverdraft represents a Overdraft event raised by the Chequebook contract.
|
||||
type ChequebookOverdraft struct {
|
||||
Deadbeat common.Address
|
||||
Raw types.Log // Blockchain specific contextual infos
|
||||
}
|
||||
|
||||
// FilterOverdraft is a free log retrieval operation binding the contract event 0x2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f978.
|
||||
//
|
||||
// Solidity: event Overdraft(deadbeat address)
|
||||
func (_Chequebook *ChequebookFilterer) FilterOverdraft(opts *bind.FilterOpts) (*ChequebookOverdraftIterator, error) {
|
||||
|
||||
logs, sub, err := _Chequebook.contract.FilterLogs(opts, "Overdraft")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ChequebookOverdraftIterator{contract: _Chequebook.contract, event: "Overdraft", logs: logs, sub: sub}, nil
|
||||
}
|
||||
|
||||
// WatchOverdraft is a free log subscription operation binding the contract event 0x2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f978.
|
||||
//
|
||||
// Solidity: event Overdraft(deadbeat address)
|
||||
func (_Chequebook *ChequebookFilterer) WatchOverdraft(opts *bind.WatchOpts, sink chan<- *ChequebookOverdraft) (event.Subscription, error) {
|
||||
|
||||
logs, sub, err := _Chequebook.contract.WatchLogs(opts, "Overdraft")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||
defer sub.Unsubscribe()
|
||||
for {
|
||||
select {
|
||||
case log := <-logs:
|
||||
// New log arrived, parse the event and forward to the user
|
||||
event := new(ChequebookOverdraft)
|
||||
if err := _Chequebook.contract.UnpackLog(event, "Overdraft", log); err != nil {
|
||||
return err
|
||||
}
|
||||
event.Raw = log
|
||||
|
||||
select {
|
||||
case sink <- event:
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
case err := <-sub.Err():
|
||||
return err
|
||||
case <-quit:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
pragma solidity ^0.4.18;
|
||||
|
||||
import "./mortal.sol";
|
||||
|
||||
/// @title Chequebook for Ethereum micropayments
|
||||
/// @author Daniel A. Nagy <daniel@ethereum.org>
|
||||
contract chequebook is mortal {
|
||||
// Cumulative paid amount in wei to each beneficiary
|
||||
mapping (address => uint256) public sent;
|
||||
|
||||
/// @notice Overdraft event
|
||||
event Overdraft(address deadbeat);
|
||||
|
||||
// Allow sending ether to the chequebook.
|
||||
function() public payable { }
|
||||
|
||||
/// @notice Cash cheque
|
||||
///
|
||||
/// @param beneficiary beneficiary address
|
||||
/// @param amount cumulative amount in wei
|
||||
/// @param sig_v signature parameter v
|
||||
/// @param sig_r signature parameter r
|
||||
/// @param sig_s signature parameter s
|
||||
/// The digital signature is calculated on the concatenated triplet of contract address, beneficiary address and cumulative amount
|
||||
function cash(address beneficiary, uint256 amount, uint8 sig_v, bytes32 sig_r, bytes32 sig_s) public {
|
||||
// Check if the cheque is old.
|
||||
// Only cheques that are more recent than the last cashed one are considered.
|
||||
require(amount > sent[beneficiary]);
|
||||
// Check the digital signature of the cheque.
|
||||
bytes32 hash = keccak256(address(this), beneficiary, amount);
|
||||
require(owner == ecrecover(hash, sig_v, sig_r, sig_s));
|
||||
// Attempt sending the difference between the cumulative amount on the cheque
|
||||
// and the cumulative amount on the last cashed cheque to beneficiary.
|
||||
uint256 diff = amount - sent[beneficiary];
|
||||
if (diff <= this.balance) {
|
||||
// update the cumulative amount before sending
|
||||
sent[beneficiary] = amount;
|
||||
beneficiary.transfer(diff);
|
||||
} else {
|
||||
// Upon failure, punish owner for writing a bounced cheque.
|
||||
// owner.sendToDebtorsPrison();
|
||||
Overdraft(owner);
|
||||
// Compensate beneficiary.
|
||||
selfdestruct(beneficiary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package contract
|
||||
|
||||
// ContractDeployedCode is used to detect suicides. This constant needs to be
|
||||
// updated when the contract code is changed.
|
||||
const ContractDeployedCode = "0x6060604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b581146100585780637bf786f81461006b578063fbf788d61461009c575b005b341561006357600080fd5b6100566100ca565b341561007657600080fd5b61008a600160a060020a03600435166100f1565b60405190815260200160405180910390f35b34156100a757600080fd5b610056600160a060020a036004351660243560ff60443516606435608435610103565b60005433600160a060020a03908116911614156100ef57600054600160a060020a0316ff5b565b60016020526000908152604090205481565b600160a060020a0385166000908152600160205260408120548190861161012957600080fd5b3087876040516c01000000000000000000000000600160a060020a03948516810282529290931690910260148301526028820152604801604051809103902091506001828686866040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f115156101cf57600080fd5b505060206040510351600054600160a060020a039081169116146101f257600080fd5b50600160a060020a03808716600090815260016020526040902054860390301631811161026257600160a060020a0387166000818152600160205260409081902088905582156108fc0290839051600060405180830381858888f19350505050151561025d57600080fd5b6102b7565b6000547f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f97890600160a060020a0316604051600160a060020a03909116815260200160405180910390a186600160a060020a0316ff5b505050505050505600a165627a7a72305820533e856fc37e3d64d1706bcc7dfb6b1d490c8d566ea498d9d01ec08965a896ca0029"
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
pragma solidity ^0.4.0;
|
||||
|
||||
import "./owned.sol";
|
||||
|
||||
contract mortal is owned {
|
||||
function kill() public {
|
||||
if (msg.sender == owner)
|
||||
selfdestruct(owner);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
pragma solidity ^0.4.0;
|
||||
|
||||
contract owned {
|
||||
address owner;
|
||||
|
||||
modifier onlyowner() {
|
||||
if (msg.sender == owner) {
|
||||
_;
|
||||
}
|
||||
}
|
||||
|
||||
function owned() public {
|
||||
owner = msg.sender;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// +build none
|
||||
|
||||
// This program generates contract/code.go, which contains the chequebook code
|
||||
// after deployment.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||
"github.com/ethereum/go-ethereum/contracts/chequebook/contract"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
var (
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
testAlloc = core.GenesisAlloc{
|
||||
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(500000000000)},
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
backend := backends.NewSimulatedBackend(testAlloc, uint64(100000000))
|
||||
auth := bind.NewKeyedTransactor(testKey)
|
||||
|
||||
// Deploy the contract, get the code.
|
||||
addr, _, _, err := contract.DeployChequebook(auth, backend)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
backend.Commit()
|
||||
code, err := backend.CodeAt(nil, addr, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(code) == 0 {
|
||||
panic("empty code")
|
||||
}
|
||||
|
||||
// Write the output file.
|
||||
content := fmt.Sprintf(`package contract
|
||||
|
||||
// ContractDeployedCode is used to detect suicides. This constant needs to be
|
||||
// updated when the contract code is changed.
|
||||
const ContractDeployedCode = "%#x"
|
||||
`, code)
|
||||
if err := ioutil.WriteFile("contract/code.go", []byte(content), 0644); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
# Swarm ENS interface
|
||||
|
||||
## Usage
|
||||
|
||||
Full documentation for the Ethereum Name Service [can be found as EIP 137](https://github.com/ethereum/EIPs/issues/137).
|
||||
This package offers a simple binding that streamlines the registration of arbitrary UTF8 domain names to swarm content hashes.
|
||||
|
||||
## Development
|
||||
|
||||
The SOL file in contract subdirectory implements the ENS root registry, a simple
|
||||
first-in, first-served registrar for the root namespace, and a simple resolver contract;
|
||||
they're used in tests, and can be used to deploy these contracts for your own purposes.
|
||||
|
||||
The solidity source code can be found at [github.com/arachnid/ens/](https://github.com/arachnid/ens/).
|
||||
|
||||
The go bindings for ENS contracts are generated using `abigen` via the go generator:
|
||||
|
||||
```shell
|
||||
go generate ./contracts/ens
|
||||
```
|
||||
|
||||
## Fallback contract support
|
||||
|
||||
In order to better support content resolution on different service providers (such as Swarm and IPFS), [EIP-1577](https://eips.ethereum.org/EIPS/eip-1577)
|
||||
was introduced and with it changes that allow applications to know _where_ content hashes are stored (i.e. if the
|
||||
requested hash resides on Swarm or IPFS).
|
||||
|
||||
The code under `contracts/ens/contract` reflects the new Public Resolver changes and the code under `fallback_contract` allows
|
||||
us to support the old contract resolution in cases where the ENS name owner did not update her Resolver contract, until the migration
|
||||
period ends (date arbitrarily set to June 1st, 2019).
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue