Compare commits
404 Commits
Author | SHA1 | Date | |
---|---|---|---|
d62e9b2857 | |||
878e35bfde | |||
2e98706a99 | |||
8c1e8de839 | |||
b26eedf9e9 | |||
44b41641f8 | |||
9ef90dbf30 | |||
d9d2a4eef9 | |||
9d67222f4e | |||
f5a68a40bf | |||
f06ae5ca6a | |||
3a0480e07d | |||
5d21667587 | |||
fdff182f11 | |||
0abcf03fde | |||
58f2ce8671 | |||
dd21f079e8 | |||
36a684ca1e | |||
bcc1234778 | |||
c1db636fb3 | |||
5b558ad936 | |||
b6d4f6b66e | |||
0ec5ab4175 | |||
0754100464 | |||
475ae8bd93 | |||
89ab8a74c0 | |||
72e62efc76 | |||
f56f969dd3 | |||
216ff5a952 | |||
7be89a7a01 | |||
59177bc8c0 | |||
c5b46a79c1 | |||
b4bc3b3c35 | |||
75e029db8b | |||
b8ced9e00b | |||
f8790b9482 | |||
8bd5bb8918 | |||
a7dfaa0bda | |||
e1dcea8bf0 | |||
b3d6304f1e | |||
f4ec85486a | |||
dfdb204b48 | |||
15fb780de6 | |||
3a4a3d080b | |||
b7ba944e88 | |||
9b59c75405 | |||
f71e85b8e2 | |||
8008c5b1fa | |||
9c6cf960b4 | |||
df206d2513 | |||
9e8cc00b73 | |||
ac5e28ea38 | |||
3b0f3483c4 | |||
7f70a70106 | |||
94e8250983 | |||
c013192ba7 | |||
0b6338321f | |||
b9c90c5581 | |||
5fefe39ba5 | |||
dfe891270a | |||
c5c5e0dbe8 | |||
3f4a875bf6 | |||
a3d263dd3a | |||
190fb8180a | |||
b02afb6b3d | |||
422604b438 | |||
57d697629d | |||
11d09fd3ba | |||
689486449d | |||
7c4a4eb58a | |||
9e71f55bfa | |||
51c3290bee | |||
738b51ae31 | |||
f03b2db7db | |||
49d1a032da | |||
765fe446cf | |||
afe0b65405 | |||
987648b0ad | |||
9504c5c360 | |||
f8a95d996f | |||
bf5c6b29fa | |||
22e3bbbf0a | |||
4ea9b62b5c | |||
6f1a600f6c | |||
de2259d27c | |||
adf007dadc | |||
4b8f56cf98 | |||
a718daa674 | |||
b9bac1f384 | |||
fc3661f89c | |||
9948724deb | |||
c702bd70ed | |||
734e00af9e | |||
b566cfdffd | |||
7a6d5d0cce | |||
0ff7380465 | |||
0ce5e113be | |||
86fe283d19 | |||
44b74cfc40 | |||
9278951a62 | |||
12f2a25d5e | |||
8927f7724a | |||
93422e9d15 | |||
5d91acccd5 | |||
9641cacea8 | |||
64571f9379 | |||
e306304414 | |||
2c37142d2f | |||
3eca7b5d27 | |||
b0b277525c | |||
ecdbb402ee | |||
9c81387bef | |||
72617a0742 | |||
db79143a13 | |||
538f763fdc | |||
d4bb3798d8 | |||
08953e42c1 | |||
b9299bbc46 | |||
9a77065948 | |||
a28093ced4 | |||
d5b79e752e | |||
7300365956 | |||
c476460cb2 | |||
028af3457d | |||
a73f3f4518 | |||
bd05968077 | |||
6e730915bd | |||
c713ea7c22 | |||
7f5f62aaa0 | |||
b2f696e025 | |||
62b43ee0d5 | |||
a2a60869c8 | |||
df89233b57 | |||
ead711779d | |||
2133f18f15 | |||
1a6ef5ae58 | |||
ad03d9801c | |||
62391ddbeb | |||
0568e81701 | |||
32b07e8b1f | |||
aca39a6498 | |||
be500b57d2 | |||
a308f012ba | |||
311419c7d6 | |||
63b18027dc | |||
a1c09b9387 | |||
05347b3d98 | |||
75aec8a28d | |||
24ef83518c | |||
46891c12ab | |||
c0010f0220 | |||
9f98628dc2 | |||
a6a14f6b71 | |||
f9eb307216 | |||
03c7d8fb31 | |||
2becb99583 | |||
ad380cd57f | |||
7b32d2a470 | |||
f40ff23b7b | |||
0ac9bbba6c | |||
d4dce43bff | |||
056183c056 | |||
8d41e885e6 | |||
8bd64f4a1c | |||
b1c3010bf2 | |||
aff986958d | |||
f49d6e5ec0 | |||
223b950944 | |||
16f0fb70f1 | |||
96fb839133 | |||
3b6c9902f3 | |||
efe123759a | |||
8eb646a96d | |||
c02d5bc5a9 | |||
71251c7296 | |||
2469c4ecd4 | |||
39b0b1a1a6 | |||
91b7349509 | |||
52a967cfab | |||
305ed955db | |||
72045dff4f | |||
46b437f39c | |||
72d5a27a39 | |||
4f6bf2f1c5 | |||
49b86a2859 | |||
cea2c80445 | |||
67bfc93053 | |||
ce2da832ac | |||
b4a4a4db71 | |||
cfbb969da8 | |||
1611815b8d | |||
b8a9457139 | |||
af16ca177f | |||
cedf8be435 | |||
d5bd38384c | |||
292cf7c649 | |||
396f1dd87b | |||
68502595f6 | |||
a978adfd7c | |||
cc9eb91d30 | |||
e39b2a2bde | |||
c8a1c0a115 | |||
4aeeddc658 | |||
e126b0836a | |||
961aa0533f | |||
54b271a86d | |||
b90cdbaa79 | |||
4d358b9fc0 | |||
1eaf66ae60 | |||
4ef5e9746b | |||
060e33fb4c | |||
46ec63b849 | |||
1cd5bf080e | |||
7c229941ac | |||
ac23073619 | |||
8e391cec43 | |||
5b2c47a575 | |||
8517dd463d | |||
22fdbee8ed | |||
1bccafe5ef | |||
2890f060b7 | |||
2ed729d38e | |||
4aee0d1994 | |||
039a9c3622 | |||
3bb9b49afb | |||
9dfca5df4b | |||
273b3741b6 | |||
85d2b0d654 | |||
7d3b26018b | |||
dbb03fe989 | |||
2c50b2c904 | |||
9bad7fa717 | |||
26f538b0e5 | |||
260b177fe3 | |||
c2c4c9f1e5 | |||
44c8b9ad37 | |||
eea66ddbbd | |||
aaf29095bb | |||
e76047e9f5 | |||
34d7503d95 | |||
9b8d727655 | |||
df6c08a485 | |||
423fd5877d | |||
8657a0d6b5 | |||
36994e4e0b | |||
c9cdf144d5 | |||
081642ed25 | |||
17589aa75f | |||
3e993ff64a | |||
f3478f2899 | |||
c55e1b495c | |||
f891fd9875 | |||
beff5fa578 | |||
aa6005b469 | |||
a7de796840 | |||
947f5f2b15 | |||
e46a01d56c | |||
7f3362595a | |||
140a7e9177 | |||
96ab8e1575 | |||
f34a3a6805 | |||
8812c4d3f9 | |||
e4232c153b | |||
389bd75142 | |||
08e5cd94a9 | |||
b7b2f60f86 | |||
530f78e22d | |||
57d9c93dcd | |||
4f56790efc | |||
78ab411aac | |||
f08eb04896 | |||
3b96c17fc1 | |||
4ac941a9fc | |||
b80c840af3 | |||
a32a2b933a | |||
04e175b8ec | |||
e8141e1685 | |||
82985075f7 | |||
b973eddd28 | |||
1a83114c74 | |||
364e485e51 | |||
28fea9c5af | |||
57fc1d21e1 | |||
cc3ef1e4f4 | |||
5183483c53 | |||
a1f8549262 | |||
e8c9579fb7 | |||
433cb564e9 | |||
8485f7cc7b | |||
61a20cb56d | |||
f088c650a5 | |||
9466b9eec5 | |||
4ac04ae0fe | |||
8f80cafa10 | |||
31a1f164d9 | |||
6bd896a97f | |||
49a7ee460e | |||
252150918c | |||
72029f0f88 | |||
52f2461774 | |||
54d0eeb494 | |||
c705aac826 | |||
c6a9616cfd | |||
8c4bc4f7ef | |||
e970d60d37 | |||
2808bc68b9 | |||
213690cdfd | |||
7527215a68 | |||
8c249cb82f | |||
a966425a1d | |||
5873c01c3d | |||
1bed5afd92 | |||
16e313699f | |||
fa538ee7ed | |||
983f92368b | |||
cc0f0e27a6 | |||
22060611fb | |||
cdfe9a3a2a | |||
5bc9ccfa0a | |||
f2eb3b1c56 | |||
7fd82a0e3e | |||
dcc4adfcd7 | |||
d9c75cd10e | |||
6814797173 | |||
59a3198382 | |||
8d2cf028a5 | |||
5f5de49cd9 | |||
ca6c8c2af4 | |||
802074cba9 | |||
32273df0ea | |||
de6facb966 | |||
22411919da | |||
a0943b8932 | |||
6bf5555c4f | |||
0b26a826e9 | |||
f7cdea2bdc | |||
702f52fb99 | |||
6069b1a5f5 | |||
fd072c2fd1 | |||
54fd263b40 | |||
2ca89ea479 | |||
1da5e0ebb0 | |||
e4a1488b2f | |||
98099d6fa7 | |||
92a90d7578 | |||
f578d41ee6 | |||
cdadf57bf9 | |||
60c062e17d | |||
25c3282cf1 | |||
1eddd332d2 | |||
c94068a8bb | |||
e3ec77f50e | |||
8d815e365c | |||
3271a5afa0 | |||
2b303e7d62 | |||
96f8be36ad | |||
38c914be64 | |||
cf47ee5339 | |||
2046d66fe5 | |||
d6ccfd92f7 | |||
5298eb7519 | |||
79c90dce20 | |||
2da6d1e047 | |||
f213ceb83f | |||
c8c3ebd593 | |||
b3f7609d7d | |||
32dafea1f0 | |||
3d7d7384ca | |||
fc4fee8649 | |||
3675b8545d | |||
50e3795eef | |||
c4e8806d9b | |||
2b54666018 | |||
c420dcb39c | |||
c0a034ec89 | |||
3d3e83ecff | |||
b02958b9c5 | |||
f9c0e093ed | |||
6f80629383 | |||
afb9e6513f | |||
e83c3ccc47 | |||
896322bf88 | |||
1b15c6da33 | |||
ce43c2366d | |||
9805288cdd | |||
a4eeeedb37 | |||
19fa9064d7 | |||
f01f3f266c | |||
c52390d078 | |||
2823ce0086 | |||
30c2b1b06d | |||
2fae1bde42 | |||
b8ca3cb7d2 | |||
7c9307c683 | |||
7641bbe535 | |||
645756cda5 | |||
d97f0372b1 | |||
de38a1dbd4 | |||
5d68400cad | |||
42b81f94ad | |||
15f24ff189 | |||
17381ecc66 | |||
b4cc7b660c | |||
4799b5abd4 |
@ -1 +1,5 @@
|
|||||||
.github
|
**/*_test.go
|
||||||
|
|
||||||
|
build/_workspace
|
||||||
|
build/_bin
|
||||||
|
tests/testdata
|
||||||
|
24
.github/CODEOWNERS
vendored
24
.github/CODEOWNERS
vendored
@ -1 +1,23 @@
|
|||||||
# To be defined
|
# Lines starting with '#' are comments.
|
||||||
|
# Each line is a file pattern followed by one or more owners.
|
||||||
|
|
||||||
|
accounts/usbwallet @karalabe
|
||||||
|
accounts/scwallet @gballet
|
||||||
|
accounts/abi @gballet
|
||||||
|
cmd/clef @holiman
|
||||||
|
cmd/puppeth @karalabe
|
||||||
|
consensus @karalabe
|
||||||
|
core/ @karalabe @holiman @rjl493456442
|
||||||
|
dashboard/ @kurkomisi
|
||||||
|
eth/ @karalabe @holiman @rjl493456442
|
||||||
|
graphql/ @gballet
|
||||||
|
les/ @zsfelfoldi @rjl493456442
|
||||||
|
light/ @zsfelfoldi @rjl493456442
|
||||||
|
mobile/ @karalabe @ligi
|
||||||
|
p2p/ @fjl @zsfelfoldi
|
||||||
|
rpc/ @fjl @holiman
|
||||||
|
p2p/simulations @zelig @janos @justelad
|
||||||
|
p2p/protocols @zelig @janos @justelad
|
||||||
|
p2p/testing @zelig @janos @justelad
|
||||||
|
signer/ @holiman
|
||||||
|
whisper/ @gballet @gluk256
|
||||||
|
46
.github/CONTRIBUTING.md
vendored
46
.github/CONTRIBUTING.md
vendored
@ -1,20 +1,40 @@
|
|||||||
## Contributing
|
# Contributing
|
||||||
|
|
||||||
Thank you for considering to help out with the source code! We welcome contributions from
|
Thank you for considering to help out with the source code! We welcome
|
||||||
anyone on the internet, and are grateful for even the smallest of fixes!
|
contributions from anyone on the internet, and are grateful for even the
|
||||||
|
smallest of fixes!
|
||||||
|
|
||||||
If you'd like to contribute to Swarm, please fork, fix, commit and send a pull request
|
If you'd like to contribute to go-ethereum, please fork, fix, commit and send a
|
||||||
for the maintainers to review and merge into the main code base. If you wish to submit more
|
pull request for the maintainers to review and merge into the main code base. If
|
||||||
complex changes though, please check up with the core devs first on [our Swarm gitter channel](https://gitter.im/ethersphere/orange-lounge)
|
you wish to submit more complex changes though, please check up with the core
|
||||||
to ensure those changes are in line with the general philosophy of the project and/or get some
|
devs first on [our gitter channel](https://gitter.im/ethereum/go-ethereum) to
|
||||||
early feedback which can make both your efforts much lighter as well as our review and merge
|
ensure those changes are in line with the general philosophy of the project
|
||||||
procedures quick and simple.
|
and/or get some early feedback which can make both your efforts much lighter as
|
||||||
|
well as our review and merge procedures quick and simple.
|
||||||
|
|
||||||
|
## Coding guidelines
|
||||||
|
|
||||||
Please make sure your contributions adhere to our coding guidelines:
|
Please make sure your contributions adhere to our coding guidelines:
|
||||||
|
|
||||||
* Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
* Code must adhere to the official Go
|
||||||
* Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
[formatting](https://golang.org/doc/effective_go.html#formatting) guidelines
|
||||||
|
(i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||||
|
* Code must be documented adhering to the official Go
|
||||||
|
[commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
||||||
* Pull requests need to be based on and opened against the `master` branch.
|
* Pull requests need to be based on and opened against the `master` branch.
|
||||||
* [Code review guidelines](https://github.com/ethersphere/swarm/blob/master/docs/Code-Review-Guidelines.md).
|
|
||||||
* Commit messages should be prefixed with the package(s) they modify.
|
* Commit messages should be prefixed with the package(s) they modify.
|
||||||
* E.g. "fuse: ignore default manifest entry"
|
* E.g. "eth, rpc: make trace configs optional"
|
||||||
|
|
||||||
|
## Can I have feature X
|
||||||
|
|
||||||
|
Before you submit a feature request, please check and make sure that it isn't
|
||||||
|
possible through some other means. The JavaScript-enabled console is a powerful
|
||||||
|
feature in the right hands. Please check our
|
||||||
|
[Wiki page](https://github.com/ethereum/go-ethereum/wiki) for more info
|
||||||
|
and help.
|
||||||
|
|
||||||
|
## Configuration, dependencies, and tests
|
||||||
|
|
||||||
|
Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide)
|
||||||
|
for more details on configuring your environment, managing project dependencies
|
||||||
|
and testing procedures.
|
||||||
|
8
.github/ISSUE_TEMPLATE.md
vendored
8
.github/ISSUE_TEMPLATE.md
vendored
@ -1,8 +1,12 @@
|
|||||||
<!-- Thanks for filing an issue! Before hitting the button, please answer these questions. It's helpful to search the existing GitHub issues first. It's likely that another user has already reported the issue you're facing, or it's a known issue that we're already aware of. Please note that this is an issue tracker reserved for bug reports and feature requests. For general questions please use the gitter channel https://gitter.im/ethereum/swarm or the ethereum stack exchange at https://ethereum.stackexchange.com. -->
|
Hi there,
|
||||||
|
|
||||||
|
Please note that this is an issue tracker reserved for bug reports and feature requests.
|
||||||
|
|
||||||
|
For general questions please use the gitter channel or the Ethereum stack exchange at https://ethereum.stackexchange.com.
|
||||||
|
|
||||||
#### System information
|
#### System information
|
||||||
|
|
||||||
Swarm version: `swarm version`
|
Geth version: `geth version`
|
||||||
OS & Version: Windows/Linux/OSX
|
OS & Version: Windows/Linux/OSX
|
||||||
Commit hash : (if `develop`)
|
Commit hash : (if `develop`)
|
||||||
|
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -24,6 +24,7 @@ build/_vendor/pkg
|
|||||||
|
|
||||||
# used by the Makefile
|
# used by the Makefile
|
||||||
/build/_workspace/
|
/build/_workspace/
|
||||||
|
/build/cache/
|
||||||
/build/bin/
|
/build/bin/
|
||||||
/geth*.zip
|
/geth*.zip
|
||||||
|
|
||||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "tests"]
|
||||||
|
path = tests/testdata
|
||||||
|
url = https://github.com/ethereum/tests
|
45
.golangci.yml
Normal file
45
.golangci.yml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# This file configures github.com/golangci/golangci-lint.
|
||||||
|
|
||||||
|
run:
|
||||||
|
timeout: 2m
|
||||||
|
tests: true
|
||||||
|
# default is true. Enables skipping of directories:
|
||||||
|
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||||
|
skip-dirs-use-default: true
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- deadcode
|
||||||
|
- goconst
|
||||||
|
- goimports
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
# - staticcheck
|
||||||
|
- unconvert
|
||||||
|
# - unused
|
||||||
|
- varcheck
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gofmt:
|
||||||
|
simplify: true
|
||||||
|
goconst:
|
||||||
|
min-len: 3 # minimum length of string constant
|
||||||
|
min-occurrences: 6 # minimum number of occurrences
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- path: crypto/blake2b/
|
||||||
|
linters:
|
||||||
|
- deadcode
|
||||||
|
- path: crypto/bn256/cloudflare
|
||||||
|
linters:
|
||||||
|
- deadcode
|
||||||
|
- path: p2p/discv5/
|
||||||
|
linters:
|
||||||
|
- deadcode
|
||||||
|
- path: core/vm/instructions_test.go
|
||||||
|
linters:
|
||||||
|
- goconst
|
123
.mailmap
Normal file
123
.mailmap
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
Jeffrey Wilcke <jeffrey@ethereum.org>
|
||||||
|
Jeffrey Wilcke <jeffrey@ethereum.org> <geffobscura@gmail.com>
|
||||||
|
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@obscura.com>
|
||||||
|
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@users.noreply.github.com>
|
||||||
|
|
||||||
|
Viktor Trón <viktor.tron@gmail.com>
|
||||||
|
|
||||||
|
Joseph Goulden <joegoulden@gmail.com>
|
||||||
|
|
||||||
|
Nick Savers <nicksavers@gmail.com>
|
||||||
|
|
||||||
|
Maran Hidskes <maran.hidskes@gmail.com>
|
||||||
|
|
||||||
|
Taylor Gerring <taylor.gerring@gmail.com>
|
||||||
|
Taylor Gerring <taylor.gerring@gmail.com> <taylor.gerring@ethereum.org>
|
||||||
|
|
||||||
|
Bas van Kervel <bas@ethdev.com>
|
||||||
|
Bas van Kervel <bas@ethdev.com> <basvankervel@ziggo.nl>
|
||||||
|
Bas van Kervel <bas@ethdev.com> <basvankervel@gmail.com>
|
||||||
|
Bas van Kervel <bas@ethdev.com> <bas-vk@users.noreply.github.com>
|
||||||
|
|
||||||
|
Sven Ehlert <sven@ethdev.com>
|
||||||
|
|
||||||
|
Vitalik Buterin <v@buterin.com>
|
||||||
|
|
||||||
|
Marian Oancea <contact@siteshop.ro>
|
||||||
|
|
||||||
|
Christoph Jentzsch <jentzsch.software@gmail.com>
|
||||||
|
|
||||||
|
Heiko Hees <heiko@heiko.org>
|
||||||
|
|
||||||
|
Alex Leverington <alex@ethdev.com>
|
||||||
|
Alex Leverington <alex@ethdev.com> <subtly@users.noreply.github.com>
|
||||||
|
|
||||||
|
Zsolt Felföldi <zsfelfoldi@gmail.com>
|
||||||
|
|
||||||
|
Gavin Wood <i@gavwood.com>
|
||||||
|
|
||||||
|
Martin Becze <mjbecze@gmail.com>
|
||||||
|
Martin Becze <mjbecze@gmail.com> <wanderer@users.noreply.github.com>
|
||||||
|
|
||||||
|
Dimitry Khokhlov <winsvega@mail.ru>
|
||||||
|
|
||||||
|
Roman Mandeleil <roman.mandeleil@gmail.com>
|
||||||
|
|
||||||
|
Alec Perseghin <aperseghin@gmail.com>
|
||||||
|
|
||||||
|
Alon Muroch <alonmuroch@gmail.com>
|
||||||
|
|
||||||
|
Arkadiy Paronyan <arkadiy@ethdev.com>
|
||||||
|
|
||||||
|
Jae Kwon <jkwon.work@gmail.com>
|
||||||
|
|
||||||
|
Aaron Kumavis <kumavis@users.noreply.github.com>
|
||||||
|
|
||||||
|
Nick Dodson <silentcicero@outlook.com>
|
||||||
|
|
||||||
|
Jason Carver <jacarver@linkedin.com>
|
||||||
|
Jason Carver <jacarver@linkedin.com> <ut96caarrs@snkmail.com>
|
||||||
|
|
||||||
|
Joseph Chow <ethereum@outlook.com>
|
||||||
|
Joseph Chow <ethereum@outlook.com> ethers <TODO>
|
||||||
|
|
||||||
|
Enrique Fynn <enriquefynn@gmail.com>
|
||||||
|
|
||||||
|
Vincent G <caktux@gmail.com>
|
||||||
|
|
||||||
|
RJ Catalano <catalanor0220@gmail.com>
|
||||||
|
RJ Catalano <catalanor0220@gmail.com> <rj@erisindustries.com>
|
||||||
|
|
||||||
|
Nchinda Nchinda <nchinda2@gmail.com>
|
||||||
|
|
||||||
|
Aron Fischer <github@aron.guru> <homotopycolimit@users.noreply.github.com>
|
||||||
|
|
||||||
|
Vlad Gluhovsky <gluk256@users.noreply.github.com>
|
||||||
|
|
||||||
|
Ville Sundell <github@solarius.fi>
|
||||||
|
|
||||||
|
Elliot Shepherd <elliot@identitii.com>
|
||||||
|
|
||||||
|
Yohann Léon <sybiload@gmail.com>
|
||||||
|
|
||||||
|
Gregg Dourgarian <greggd@tempworks.com>
|
||||||
|
|
||||||
|
Casey Detrio <cdetrio@gmail.com>
|
||||||
|
|
||||||
|
Jens Agerberg <github@agerberg.me>
|
||||||
|
|
||||||
|
Nick Johnson <arachnid@notdot.net>
|
||||||
|
|
||||||
|
Henning Diedrich <hd@eonblast.com>
|
||||||
|
Henning Diedrich <hd@eonblast.com> Drake Burroughs <wildfyre@hotmail.com>
|
||||||
|
|
||||||
|
Felix Lange <fjl@twurst.com>
|
||||||
|
Felix Lange <fjl@twurst.com> <fjl@users.noreply.github.com>
|
||||||
|
|
||||||
|
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||||
|
|
||||||
|
Louis Holbrook <dev@holbrook.no>
|
||||||
|
Louis Holbrook <dev@holbrook.no> <nolash@users.noreply.github.com>
|
||||||
|
|
||||||
|
Thomas Bocek <tom@tomp2p.net>
|
||||||
|
|
||||||
|
Victor Tran <vu.tran54@gmail.com>
|
||||||
|
|
||||||
|
Justin Drake <drakefjustin@gmail.com>
|
||||||
|
|
||||||
|
Frank Wang <eternnoir@gmail.com>
|
||||||
|
|
||||||
|
Gary Rong <garyrong0905@gmail.com>
|
||||||
|
|
||||||
|
Guillaume Nicolas <guin56@gmail.com>
|
||||||
|
|
||||||
|
Sorin Neacsu <sorin.neacsu@gmail.com>
|
||||||
|
Sorin Neacsu <sorin.neacsu@gmail.com> <sorin@users.noreply.github.com>
|
||||||
|
|
||||||
|
Valentin Wüstholz <wuestholz@gmail.com>
|
||||||
|
Valentin Wüstholz <wuestholz@gmail.com> <wuestholz@users.noreply.github.com>
|
||||||
|
|
||||||
|
Armin Braun <me@obrown.io>
|
||||||
|
|
||||||
|
Ernesto del Toro <ernesto.deltoro@gmail.com>
|
||||||
|
Ernesto del Toro <ernesto.deltoro@gmail.com> <ernestodeltoro@users.noreply.github.com>
|
@ -1,14 +0,0 @@
|
|||||||
# .readthedocs.yml
|
|
||||||
# Read the Docs configuration file
|
|
||||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
sphinx:
|
|
||||||
configuration: docs/swarm-guide/contents/conf.py
|
|
||||||
builder: html
|
|
||||||
|
|
||||||
python:
|
|
||||||
version: 3.7
|
|
||||||
install:
|
|
||||||
- requirements: docs/swarm-guide/requirements.txt
|
|
202
.travis.yml
202
.travis.yml
@ -1,37 +1,63 @@
|
|||||||
language: go
|
language: go
|
||||||
go_import_path: github.com/ethersphere/swarm
|
go_import_path: github.com/ethereum/go-ethereum
|
||||||
sudo: false
|
sudo: false
|
||||||
branches:
|
jobs:
|
||||||
only:
|
|
||||||
- master
|
|
||||||
- /v(\d+\.)(\d+\.)(\d)/
|
|
||||||
matrix:
|
|
||||||
include:
|
include:
|
||||||
- os: linux
|
# This builder only tests code linters on latest version of Go
|
||||||
dist: trusty
|
- stage: lint
|
||||||
sudo: required
|
os: linux
|
||||||
go: 1.11.x
|
dist: xenial
|
||||||
|
go: 1.13.x
|
||||||
|
env:
|
||||||
|
- lint
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
|
script:
|
||||||
|
- go run build/ci.go lint
|
||||||
|
|
||||||
|
- stage: build
|
||||||
|
os: linux
|
||||||
|
dist: xenial
|
||||||
|
go: 1.11.x
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
script:
|
||||||
|
- go run build/ci.go install
|
||||||
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
|
- stage: build
|
||||||
|
os: linux
|
||||||
|
dist: xenial
|
||||||
|
go: 1.12.x
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
script:
|
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 install
|
||||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
# These are the latest Go versions.
|
# These are the latest Go versions.
|
||||||
- os: linux
|
- stage: build
|
||||||
dist: trusty
|
os: linux
|
||||||
sudo: required
|
arch: amd64
|
||||||
go: 1.12.x
|
dist: xenial
|
||||||
|
go: 1.13.x
|
||||||
script:
|
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 install
|
||||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
- os: osx
|
- stage: build
|
||||||
go: 1.12.x
|
if: type = pull_request
|
||||||
|
os: linux
|
||||||
|
arch: arm64
|
||||||
|
dist: xenial
|
||||||
|
go: 1.13.x
|
||||||
|
script:
|
||||||
|
- go run build/ci.go install
|
||||||
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
|
- stage: build
|
||||||
|
os: osx
|
||||||
|
go: 1.13.x
|
||||||
script:
|
script:
|
||||||
- echo "Increase the maximum number of open file descriptors on macOS"
|
- echo "Increase the maximum number of open file descriptors on macOS"
|
||||||
- NOFILE=20480
|
- NOFILE=20480
|
||||||
@ -45,22 +71,16 @@ matrix:
|
|||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
# This builder only tests code linters on latest version of Go
|
|
||||||
- os: linux
|
|
||||||
dist: trusty
|
|
||||||
go: 1.12.x
|
|
||||||
env:
|
|
||||||
- lint
|
|
||||||
script:
|
|
||||||
- go run build/ci.go lint
|
|
||||||
|
|
||||||
# This builder does the Ubuntu PPA upload
|
# This builder does the Ubuntu PPA upload
|
||||||
- if: type = push
|
- stage: build
|
||||||
|
if: type = push
|
||||||
os: linux
|
os: linux
|
||||||
dist: trusty
|
dist: xenial
|
||||||
go: 1.12.x
|
go: 1.13.x
|
||||||
env:
|
env:
|
||||||
- ubuntu-ppa
|
- ubuntu-ppa
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
@ -72,16 +92,19 @@ matrix:
|
|||||||
- python-paramiko
|
- python-paramiko
|
||||||
script:
|
script:
|
||||||
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
|
- echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts
|
||||||
- go run build/ci.go debsrc -upload ethereum/ethereum -sftp-user ethswarm -signer "Ethereum Swarm Linux Builder <swarm@ethereum.org>"
|
- go run build/ci.go debsrc -goversion 1.13.4 -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder <geth-ci@ethereum.org>"
|
||||||
|
|
||||||
# This builder does the Linux Azure uploads
|
# This builder does the Linux Azure uploads
|
||||||
- if: type = push
|
- stage: build
|
||||||
|
if: type = push
|
||||||
os: linux
|
os: linux
|
||||||
dist: trusty
|
dist: xenial
|
||||||
sudo: required
|
sudo: required
|
||||||
go: 1.12.x
|
go: 1.13.x
|
||||||
env:
|
env:
|
||||||
- azure-linux
|
- azure-linux
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
@ -89,66 +112,129 @@ matrix:
|
|||||||
script:
|
script:
|
||||||
# Build for the primary platforms that Trusty can manage
|
# Build for the primary platforms that Trusty can manage
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload ethswarm/builds
|
- go run build/ci.go archive -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
- go run build/ci.go install -arch 386
|
- go run build/ci.go install -arch 386
|
||||||
- go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -upload ethswarm/builds
|
- go run build/ci.go archive -arch 386 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
# Switch over GCC to cross compilation (breaks 386, hence why do it here only)
|
# Switch over GCC to cross compilation (breaks 386, hence why do it here only)
|
||||||
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross
|
- sudo -E apt-get -yq --no-install-suggests --no-install-recommends --force-yes install gcc-arm-linux-gnueabi libc6-dev-armel-cross gcc-arm-linux-gnueabihf libc6-dev-armhf-cross gcc-aarch64-linux-gnu libc6-dev-arm64-cross
|
||||||
- sudo ln -s /usr/include/asm-generic /usr/include/asm
|
- sudo ln -s /usr/include/asm-generic /usr/include/asm
|
||||||
|
|
||||||
- GOARM=5 go run build/ci.go install -arch arm -cc arm-linux-gnueabi-gcc
|
- GOARM=5 go run build/ci.go install -arch arm -cc arm-linux-gnueabi-gcc
|
||||||
- GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload ethswarm/builds
|
- GOARM=5 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
- GOARM=6 go run build/ci.go install -arch arm -cc arm-linux-gnueabi-gcc
|
- GOARM=6 go run build/ci.go install -arch arm -cc arm-linux-gnueabi-gcc
|
||||||
- GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload ethswarm/builds
|
- GOARM=6 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
- GOARM=7 go run build/ci.go install -arch arm -cc arm-linux-gnueabihf-gcc
|
- GOARM=7 go run build/ci.go install -arch arm -cc arm-linux-gnueabihf-gcc
|
||||||
- GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload ethswarm/builds
|
- GOARM=7 go run build/ci.go archive -arch arm -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
- go run build/ci.go install -arch arm64 -cc aarch64-linux-gnu-gcc
|
- go run build/ci.go install -arch arm64 -cc aarch64-linux-gnu-gcc
|
||||||
- go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload ethswarm/builds
|
- 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
|
# This builder does the Linux Azure MIPS xgo uploads
|
||||||
- if: type = push
|
- stage: build
|
||||||
|
if: type = push
|
||||||
os: linux
|
os: linux
|
||||||
dist: trusty
|
dist: xenial
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
go: 1.12.x
|
go: 1.13.x
|
||||||
env:
|
env:
|
||||||
- azure-linux-mips
|
- azure-linux-mips
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v
|
- go run build/ci.go xgo --alltools -- --targets=linux/mips --ldflags '-extldflags "-static"' -v
|
||||||
- for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done
|
- for bin in build/bin/*-linux-mips; do mv -f "${bin}" "${bin/-linux-mips/}"; done
|
||||||
- go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -upload ethswarm/builds
|
- go run build/ci.go archive -arch mips -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
- go run build/ci.go xgo --alltools -- --targets=linux/mipsle --ldflags '-extldflags "-static"' -v
|
- go run build/ci.go xgo --alltools -- --targets=linux/mipsle --ldflags '-extldflags "-static"' -v
|
||||||
- for bin in build/bin/*-linux-mipsle; do mv -f "${bin}" "${bin/-linux-mipsle/}"; done
|
- for bin in build/bin/*-linux-mipsle; do mv -f "${bin}" "${bin/-linux-mipsle/}"; done
|
||||||
- go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -upload ethswarm/builds
|
- go run build/ci.go archive -arch mipsle -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
- go run build/ci.go xgo --alltools -- --targets=linux/mips64 --ldflags '-extldflags "-static"' -v
|
- go run build/ci.go xgo --alltools -- --targets=linux/mips64 --ldflags '-extldflags "-static"' -v
|
||||||
- for bin in build/bin/*-linux-mips64; do mv -f "${bin}" "${bin/-linux-mips64/}"; done
|
- for bin in build/bin/*-linux-mips64; do mv -f "${bin}" "${bin/-linux-mips64/}"; done
|
||||||
- go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY -upload ethswarm/builds
|
- go run build/ci.go archive -arch mips64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
- go run build/ci.go xgo --alltools -- --targets=linux/mips64le --ldflags '-extldflags "-static"' -v
|
- go run build/ci.go xgo --alltools -- --targets=linux/mips64le --ldflags '-extldflags "-static"' -v
|
||||||
- for bin in build/bin/*-linux-mips64le; do mv -f "${bin}" "${bin/-linux-mips64le/}"; done
|
- for bin in build/bin/*-linux-mips64le; do mv -f "${bin}" "${bin/-linux-mips64le/}"; done
|
||||||
- go run build/ci.go archive -arch mips64le -type tar -signer LINUX_SIGNING_KEY -upload ethswarm/builds
|
- 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
|
||||||
|
- stage: build
|
||||||
|
if: type = push
|
||||||
|
os: linux
|
||||||
|
dist: xenial
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- oracle-java8-installer
|
||||||
|
- oracle-java8-set-default
|
||||||
|
language: android
|
||||||
|
android:
|
||||||
|
components:
|
||||||
|
- platform-tools
|
||||||
|
- tools
|
||||||
|
- android-15
|
||||||
|
- android-19
|
||||||
|
- android-24
|
||||||
|
env:
|
||||||
|
- azure-android
|
||||||
|
- maven-android
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
|
before_install:
|
||||||
|
- curl https://dl.google.com/go/go1.13.linux-amd64.tar.gz | tar -xz
|
||||||
|
- export PATH=`pwd`/go/bin:$PATH
|
||||||
|
- export GOROOT=`pwd`/go
|
||||||
|
- export GOPATH=$HOME/go
|
||||||
|
script:
|
||||||
|
# Build the Android archive and upload it to Maven Central and Azure
|
||||||
|
- curl https://dl.google.com/android/repository/android-ndk-r19b-linux-x86_64.zip -o android-ndk-r19b.zip
|
||||||
|
- unzip -q android-ndk-r19b.zip && rm android-ndk-r19b.zip
|
||||||
|
- mv android-ndk-r19b $ANDROID_HOME/ndk-bundle
|
||||||
|
|
||||||
|
- mkdir -p $GOPATH/src/github.com/ethereum
|
||||||
|
- ln -s `pwd` $GOPATH/src/github.com/ethereum/go-ethereum
|
||||||
|
- 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
|
# This builder does the OSX Azure, iOS CocoaPods and iOS Azure uploads
|
||||||
- if: type = push
|
- stage: build
|
||||||
|
if: type = push
|
||||||
os: osx
|
os: osx
|
||||||
go: 1.12.x
|
go: 1.13.x
|
||||||
env:
|
env:
|
||||||
- azure-osx
|
- azure-osx
|
||||||
|
- azure-ios
|
||||||
|
- cocoapods-ios
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload ethswarm/builds
|
- go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds
|
||||||
|
|
||||||
|
# Build the iOS framework and upload it to CocoaPods and Azure
|
||||||
|
- gem uninstall cocoapods -a -x
|
||||||
|
- gem install cocoapods
|
||||||
|
|
||||||
|
- mv ~/.cocoapods/repos/master ~/.cocoapods/repos/master.bak
|
||||||
|
- sed -i '.bak' 's/repo.join/!repo.join/g' $(dirname `gem which cocoapods`)/cocoapods/sources_manager.rb
|
||||||
|
- if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then git clone --depth=1 https://github.com/CocoaPods/Specs.git ~/.cocoapods/repos/master && pod setup --verbose; fi
|
||||||
|
|
||||||
|
- xctool -version
|
||||||
|
- xcrun simctl list
|
||||||
|
|
||||||
|
# Workaround for https://github.com/golang/go/issues/23749
|
||||||
|
- export CGO_CFLAGS_ALLOW='-fmodules|-fblocks|-fobjc-arc'
|
||||||
|
- 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
|
# This builder does the Azure archive purges to avoid accumulating junk
|
||||||
- if: type = cron
|
- stage: build
|
||||||
|
if: type = cron
|
||||||
os: linux
|
os: linux
|
||||||
dist: trusty
|
dist: xenial
|
||||||
go: 1.12.x
|
go: 1.13.x
|
||||||
env:
|
env:
|
||||||
- azure-purge
|
- azure-purge
|
||||||
|
git:
|
||||||
|
submodules: false # avoid cloning ethereum/tests
|
||||||
script:
|
script:
|
||||||
- go run build/ci.go purge -store ethswarm/builds -days 14
|
- go run build/ci.go purge -store gethstore/builds -days 14
|
||||||
|
402
AUTHORS
402
AUTHORS
@ -1,35 +1,369 @@
|
|||||||
# Core team members
|
# This is the official list of go-ethereum authors for copyright purposes.
|
||||||
|
|
||||||
Viktor Trón - @zelig
|
a e r t h <aerth@users.noreply.github.com>
|
||||||
Louis Holbrook - @nolash
|
Abel Nieto <abel.nieto90@gmail.com>
|
||||||
Lewis Marshall - @lmars
|
Abel Nieto <anietoro@uwaterloo.ca>
|
||||||
Anton Evangelatov - @nonsense
|
Adam Babik <a.babik@designfortress.com>
|
||||||
Janoš Guljaš - @janos
|
Aditya <adityasripal@gmail.com>
|
||||||
Balint Gabor - @gbalint
|
Adrià Cidre <adria.cidre@gmail.com>
|
||||||
Elad Nachmias - @justelad
|
Afri Schoedon <5chdn@users.noreply.github.com>
|
||||||
Daniel A. Nagy - @nagydani
|
Agustin Armellini Fischer <armellini13@gmail.com>
|
||||||
Aron Fischer - @homotopycolimit
|
Airead <fgh1987168@gmail.com>
|
||||||
Fabio Barone - @holisticode
|
Alan Chen <alanchchen@users.noreply.github.com>
|
||||||
Zahoor Mohamed - @jmozah
|
Alejandro Isaza <alejandro.isaza@gmail.com>
|
||||||
Zsolt Felföldi - @zsfelfoldi
|
Ales Katona <ales@coinbase.com>
|
||||||
|
Alex Leverington <alex@ethdev.com>
|
||||||
# External contributors
|
Alex Wu <wuyiding@gmail.com>
|
||||||
|
Alexandre Van de Sande <alex.vandesande@ethdev.com>
|
||||||
Kiel Barry
|
Ali Hajimirza <Ali92hm@users.noreply.github.com>
|
||||||
Gary Rong
|
am2rican5 <am2rican5@gmail.com>
|
||||||
Jared Wasinger
|
Andrea Franz <andrea@gravityblast.com>
|
||||||
Leon Stanko
|
Andrey Petrov <andrey.petrov@shazow.net>
|
||||||
Javier Peletier [epiclabs.io]
|
Andrey Petrov <shazow@gmail.com>
|
||||||
Bartek Borkowski [tungsten-labs.com]
|
ANOTHEL <anothel1@naver.com>
|
||||||
Shane Howley [mainframe.com]
|
Antoine Rondelet <rondelet.antoine@gmail.com>
|
||||||
Doug Leonard [mainframe.com]
|
Anton Evangelatov <anton.evangelatov@gmail.com>
|
||||||
Ivan Daniluk [status.im]
|
Antonio Salazar Cardozo <savedfastcool@gmail.com>
|
||||||
Felix Lange [EF]
|
Arba Sasmoyo <arba.sasmoyo@gmail.com>
|
||||||
Martin Holst Swende [EF]
|
Armani Ferrante <armaniferrante@berkeley.edu>
|
||||||
Guillaume Ballet [EF]
|
Armin Braun <me@obrown.io>
|
||||||
ligi [EF]
|
Aron Fischer <github@aron.guru>
|
||||||
Christopher Dro [blick-labs.com]
|
atsushi-ishibashi <atsushi.ishibashi@finatext.com>
|
||||||
Sergii Bomko [ledgerleopard.com]
|
ayeowch <ayeowch@gmail.com>
|
||||||
Domino Valdano
|
b00ris <b00ris@mail.ru>
|
||||||
Rafael Matias
|
bailantaotao <Edwin@maicoin.com>
|
||||||
Coogan Brennan
|
baizhenxuan <nkbai@163.com>
|
||||||
|
Balint Gabor <balint.g@gmail.com>
|
||||||
|
Bas van Kervel <bas@ethdev.com>
|
||||||
|
Benjamin Brent <benjamin@benjaminbrent.com>
|
||||||
|
benma <mbencun@gmail.com>
|
||||||
|
Benoit Verkindt <benoit.verkindt@gmail.com>
|
||||||
|
bloonfield <bloonfield@163.com>
|
||||||
|
Bo <bohende@gmail.com>
|
||||||
|
Bo Ye <boy.e.computer.1982@outlook.com>
|
||||||
|
Bob Glickstein <bobg@users.noreply.github.com>
|
||||||
|
Brent <bmperrea@gmail.com>
|
||||||
|
Brian Schroeder <bts@gmail.com>
|
||||||
|
Bruno Škvorc <bruno@skvorc.me>
|
||||||
|
C. Brown <hackdom@majoolr.io>
|
||||||
|
Caesar Chad <BLUE.WEB.GEEK@gmail.com>
|
||||||
|
Casey Detrio <cdetrio@gmail.com>
|
||||||
|
CDsigma <cdsigma271@gmail.com>
|
||||||
|
changhong <changhong.yu@shanbay.com>
|
||||||
|
Chase Wright <mysticryuujin@gmail.com>
|
||||||
|
Chen Quan <terasum@163.com>
|
||||||
|
chenyufeng <yufengcode@gmail.com>
|
||||||
|
Christian Muehlhaeuser <muesli@gmail.com>
|
||||||
|
Christoph Jentzsch <jentzsch.software@gmail.com>
|
||||||
|
cong <ackratos@users.noreply.github.com>
|
||||||
|
Corey Lin <514971757@qq.com>
|
||||||
|
cpusoft <cpusoft@live.com>
|
||||||
|
Crispin Flowerday <crispin@bitso.com>
|
||||||
|
croath <croathliu@gmail.com>
|
||||||
|
cui <523516579@qq.com>
|
||||||
|
Dan Kinsley <dan@joincivil.com>
|
||||||
|
Daniel A. Nagy <nagy.da@gmail.com>
|
||||||
|
Daniel Sloof <goapsychadelic@gmail.com>
|
||||||
|
Darrel Herbst <dherbst@gmail.com>
|
||||||
|
Dave Appleton <calistralabs@gmail.com>
|
||||||
|
Dave McGregor <dave.s.mcgregor@gmail.com>
|
||||||
|
David Huie <dahuie@gmail.com>
|
||||||
|
Derek Gottfrid <derek@codecubed.com>
|
||||||
|
Diego Siqueira <DiSiqueira@users.noreply.github.com>
|
||||||
|
Diep Pham <mrfavadi@gmail.com>
|
||||||
|
dipingxian2 <39109351+dipingxian2@users.noreply.github.com>
|
||||||
|
dm4 <sunrisedm4@gmail.com>
|
||||||
|
Dmitrij Koniajev <dimchansky@gmail.com>
|
||||||
|
Dmitry Shulyak <yashulyak@gmail.com>
|
||||||
|
Domino Valdano <dominoplural@gmail.com>
|
||||||
|
Domino Valdano <jeff@okcupid.com>
|
||||||
|
Dragan Milic <dragan@netice9.com>
|
||||||
|
dragonvslinux <35779158+dragononcrypto@users.noreply.github.com>
|
||||||
|
Egon Elbre <egonelbre@gmail.com>
|
||||||
|
Elad <theman@elad.im>
|
||||||
|
Eli <elihanover@yahoo.com>
|
||||||
|
Elias Naur <elias.naur@gmail.com>
|
||||||
|
Elliot Shepherd <elliot@identitii.com>
|
||||||
|
Emil <mursalimovemeel@gmail.com>
|
||||||
|
emile <emile@users.noreply.github.com>
|
||||||
|
Enrique Fynn <enriquefynn@gmail.com>
|
||||||
|
Enrique Fynn <me@enriquefynn.com>
|
||||||
|
EOS Classic <info@eos-classic.io>
|
||||||
|
Erichin <erichinbato@gmail.com>
|
||||||
|
Ernesto del Toro <ernesto.deltoro@gmail.com>
|
||||||
|
Ethan Buchman <ethan@coinculture.info>
|
||||||
|
ethersphere <thesw@rm.eth>
|
||||||
|
Eugene Valeyev <evgen.povt@gmail.com>
|
||||||
|
Evangelos Pappas <epappas@evalonlabs.com>
|
||||||
|
Evgeny <awesome.observer@yandex.com>
|
||||||
|
Evgeny Danilenko <6655321@bk.ru>
|
||||||
|
evgk <evgeniy.kamyshev@gmail.com>
|
||||||
|
Fabian Vogelsteller <fabian@frozeman.de>
|
||||||
|
Fabio Barone <fabio.barone.co@gmail.com>
|
||||||
|
Fabio Berger <fabioberger1991@gmail.com>
|
||||||
|
FaceHo <facehoshi@gmail.com>
|
||||||
|
Felix Lange <fjl@twurst.com>
|
||||||
|
Ferenc Szabo <frncmx@gmail.com>
|
||||||
|
ferhat elmas <elmas.ferhat@gmail.com>
|
||||||
|
Fiisio <liangcszzu@163.com>
|
||||||
|
Frank Szendzielarz <33515470+FrankSzendzielarz@users.noreply.github.com>
|
||||||
|
Frank Wang <eternnoir@gmail.com>
|
||||||
|
Franklin <mr_franklin@126.com>
|
||||||
|
Furkan KAMACI <furkankamaci@gmail.com>
|
||||||
|
GagziW <leon.stanko@rwth-aachen.de>
|
||||||
|
Gary Rong <garyrong0905@gmail.com>
|
||||||
|
George Ornbo <george@shapeshed.com>
|
||||||
|
Gregg Dourgarian <greggd@tempworks.com>
|
||||||
|
Guilherme Salgado <gsalgado@gmail.com>
|
||||||
|
Guillaume Ballet <gballet@gmail.com>
|
||||||
|
Guillaume Nicolas <guin56@gmail.com>
|
||||||
|
GuiltyMorishita <morilliantblue@gmail.com>
|
||||||
|
Gus <yo@soygus.com>
|
||||||
|
Gustav Simonsson <gustav.simonsson@gmail.com>
|
||||||
|
Gísli Kristjánsson <gislik@hamstur.is>
|
||||||
|
Ha ĐANG <dvietha@gmail.com>
|
||||||
|
HackyMiner <hackyminer@gmail.com>
|
||||||
|
hadv <dvietha@gmail.com>
|
||||||
|
Hao Bryan Cheng <haobcheng@gmail.com>
|
||||||
|
HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
|
||||||
|
Henning Diedrich <hd@eonblast.com>
|
||||||
|
holisticode <holistic.computing@gmail.com>
|
||||||
|
Hongbin Mao <hello2mao@gmail.com>
|
||||||
|
Hsien-Tang Kao <htkao@pm.me>
|
||||||
|
Husam Ibrahim <39692071+HusamIbrahim@users.noreply.github.com>
|
||||||
|
hydai <z54981220@gmail.com>
|
||||||
|
Hyung-Kyu Hqueue Choi <hyungkyu.choi@gmail.com>
|
||||||
|
Ian Macalinao <me@ian.pw>
|
||||||
|
Ian Norden <iannordenn@gmail.com>
|
||||||
|
Isidoro Ghezzi <isidoro.ghezzi@icloud.com>
|
||||||
|
Iskander (Alex) Sharipov <quasilyte@gmail.com>
|
||||||
|
Ivan Daniluk <ivan.daniluk@gmail.com>
|
||||||
|
Ivo Georgiev <ivo@strem.io>
|
||||||
|
Jae Kwon <jkwon.work@gmail.com>
|
||||||
|
Jamie Pitts <james.pitts@gmail.com>
|
||||||
|
Janos Guljas <janos@resenje.org>
|
||||||
|
Janoš Guljaš <janos@users.noreply.github.com>
|
||||||
|
Jason Carver <jacarver@linkedin.com>
|
||||||
|
Javier Peletier <jm@epiclabs.io>
|
||||||
|
Javier Peletier <jpeletier@users.noreply.github.com>
|
||||||
|
Javier Sagredo <jasataco@gmail.com>
|
||||||
|
Jay <codeholic.arena@gmail.com>
|
||||||
|
Jay Guo <guojiannan1101@gmail.com>
|
||||||
|
Jaynti Kanani <jdkanani@gmail.com>
|
||||||
|
Jeff Prestes <jeffprestes@gmail.com>
|
||||||
|
Jeff R. Allen <jra@nella.org>
|
||||||
|
Jeffery Robert Walsh <rlxrlps@gmail.com>
|
||||||
|
Jeffrey Wilcke <jeffrey@ethereum.org>
|
||||||
|
Jens Agerberg <github@agerberg.me>
|
||||||
|
Jeremy McNevin <jeremy.mcnevin@optum.com>
|
||||||
|
Jeremy Schlatter <jeremy.schlatter@gmail.com>
|
||||||
|
Jerzy Lasyk <jerzylasyk@gmail.com>
|
||||||
|
Jia Chenhui <jiachenhui1989@gmail.com>
|
||||||
|
Jim McDonald <Jim@mcdee.net>
|
||||||
|
jkcomment <jkcomment@gmail.com>
|
||||||
|
Joel Burget <joelburget@gmail.com>
|
||||||
|
John C. Vernaleo <john@netpurgatory.com>
|
||||||
|
Johns Beharry <johns@peakshift.com>
|
||||||
|
Jonas <felberj@users.noreply.github.com>
|
||||||
|
Jonathan Brown <jbrown@bluedroplet.com>
|
||||||
|
JoranHonig <JoranHonig@users.noreply.github.com>
|
||||||
|
Jordan Krage <jmank88@gmail.com>
|
||||||
|
Joseph Chow <ethereum@outlook.com>
|
||||||
|
jtakalai <juuso.takalainen@streamr.com>
|
||||||
|
JU HYEONG PARK <dkdkajej@gmail.com>
|
||||||
|
Justin Clark-Casey <justincc@justincc.org>
|
||||||
|
Justin Drake <drakefjustin@gmail.com>
|
||||||
|
jwasinger <j-wasinger@hotmail.com>
|
||||||
|
ken10100147 <sunhongping@kanjian.com>
|
||||||
|
Kenji Siu <kenji@isuntv.com>
|
||||||
|
Kenso Trabing <kenso.trabing@bloomwebsite.com>
|
||||||
|
Kenso Trabing <ktrabing@acm.org>
|
||||||
|
Kevin <denk.kevin@web.de>
|
||||||
|
kevin.xu <cming.xu@gmail.com>
|
||||||
|
kiel barry <kiel.j.barry@gmail.com>
|
||||||
|
kimmylin <30611210+kimmylin@users.noreply.github.com>
|
||||||
|
Kitten King <53072918+kittenking@users.noreply.github.com>
|
||||||
|
knarfeh <hejun1874@gmail.com>
|
||||||
|
Kobi Gurkan <kobigurk@gmail.com>
|
||||||
|
Konrad Feldmeier <konrad@brainbot.com>
|
||||||
|
Kris Shinn <raggamuffin.music@gmail.com>
|
||||||
|
Kurkó Mihály <kurkomisi@users.noreply.github.com>
|
||||||
|
Kushagra Sharma <ksharm01@gmail.com>
|
||||||
|
Kwuaint <34888408+kwuaint@users.noreply.github.com>
|
||||||
|
Kyuntae Ethan Kim <ethan.kyuntae.kim@gmail.com>
|
||||||
|
ledgerwatch <akhounov@gmail.com>
|
||||||
|
Lefteris Karapetsas <lefteris@refu.co>
|
||||||
|
Leif Jurvetson <leijurv@gmail.com>
|
||||||
|
Leo Shklovskii <leo@thermopylae.net>
|
||||||
|
LeoLiao <leofantast@gmail.com>
|
||||||
|
Lewis Marshall <lewis@lmars.net>
|
||||||
|
lhendre <lhendre2@gmail.com>
|
||||||
|
Liang Ma <liangma.ul@gmail.com>
|
||||||
|
Liang Ma <liangma@liangbit.com>
|
||||||
|
Liang ZOU <liang.d.zou@gmail.com>
|
||||||
|
libotony <liboliqi@gmail.com>
|
||||||
|
ligi <ligi@ligi.de>
|
||||||
|
Lio李欧 <lionello@users.noreply.github.com>
|
||||||
|
Lorenzo Manacorda <lorenzo@kinvolk.io>
|
||||||
|
Louis Holbrook <dev@holbrook.no>
|
||||||
|
Luca Zeug <luclu@users.noreply.github.com>
|
||||||
|
Magicking <s@6120.eu>
|
||||||
|
manlio <manlio.poltronieri@gmail.com>
|
||||||
|
Maran Hidskes <maran.hidskes@gmail.com>
|
||||||
|
Marek Kotewicz <marek.kotewicz@gmail.com>
|
||||||
|
Marius van der Wijden <m.vanderwijden@live.de>
|
||||||
|
Mark <markya0616@gmail.com>
|
||||||
|
Mark Rushakoff <mark.rushakoff@gmail.com>
|
||||||
|
mark.lin <mark@maicoin.com>
|
||||||
|
Martin Alex Philip Dawson <u1356770@gmail.com>
|
||||||
|
Martin Holst Swende <martin@swende.se>
|
||||||
|
Martin Klepsch <martinklepsch@googlemail.com>
|
||||||
|
Mats Julian Olsen <mats@plysjbyen.net>
|
||||||
|
Matt K <1036969+mkrump@users.noreply.github.com>
|
||||||
|
Matthew Di Ferrante <mattdf@users.noreply.github.com>
|
||||||
|
Matthew Halpern <matthalp@gmail.com>
|
||||||
|
Matthew Halpern <matthalp@google.com>
|
||||||
|
Matthew Wampler-Doty <matthew.wampler.doty@gmail.com>
|
||||||
|
Max Sistemich <mafrasi2@googlemail.com>
|
||||||
|
Maximilian Meister <mmeister@suse.de>
|
||||||
|
Micah Zoltu <micah@zoltu.net>
|
||||||
|
Michael Ruminer <michael.ruminer+github@gmail.com>
|
||||||
|
Miguel Mota <miguelmota2@gmail.com>
|
||||||
|
Miya Chen <miyatlchen@gmail.com>
|
||||||
|
Mohanson <mohanson@outlook.com>
|
||||||
|
mr_franklin <mr_franklin@126.com>
|
||||||
|
Mymskmkt <1847234666@qq.com>
|
||||||
|
Nalin Bhardwaj <nalinbhardwaj@nibnalin.me>
|
||||||
|
Nchinda Nchinda <nchinda2@gmail.com>
|
||||||
|
necaremus <necaremus@gmail.com>
|
||||||
|
needkane <604476380@qq.com>
|
||||||
|
Nguyen Kien Trung <trung.n.k@gmail.com>
|
||||||
|
Nguyen Sy Thanh Son <thanhson1085@gmail.com>
|
||||||
|
Nick Dodson <silentcicero@outlook.com>
|
||||||
|
Nick Johnson <arachnid@notdot.net>
|
||||||
|
Nicolas Guillaume <gunicolas@sqli.com>
|
||||||
|
Nilesh Trivedi <nilesh@hypertrack.io>
|
||||||
|
Nimrod Gutman <nimrod.gutman@gmail.com>
|
||||||
|
njupt-moon <1015041018@njupt.edu.cn>
|
||||||
|
nkbai <nkbai@163.com>
|
||||||
|
nobody <ddean2009@163.com>
|
||||||
|
Noman <noman@noman.land>
|
||||||
|
Oleg Kovalov <iamolegkovalov@gmail.com>
|
||||||
|
Oli Bye <olibye@users.noreply.github.com>
|
||||||
|
Osuke <arget-fee.free.dgm@hotmail.co.jp>
|
||||||
|
Paul Berg <hello@paulrberg.com>
|
||||||
|
Paul Litvak <litvakpol@012.net.il>
|
||||||
|
Paulo L F Casaretto <pcasaretto@gmail.com>
|
||||||
|
Paweł Bylica <chfast@gmail.com>
|
||||||
|
Pedro Pombeiro <PombeirP@users.noreply.github.com>
|
||||||
|
Peter Broadhurst <peter@themumbles.net>
|
||||||
|
Peter Pratscher <pratscher@gmail.com>
|
||||||
|
Petr Mikusek <petr@mikusek.info>
|
||||||
|
Philip Schlump <pschlump@gmail.com>
|
||||||
|
Pierre Neter <pierreneter@gmail.com>
|
||||||
|
PilkyuJung <anothel1@naver.com>
|
||||||
|
protolambda <proto@protolambda.com>
|
||||||
|
Péter Szilágyi <peterke@gmail.com>
|
||||||
|
qd-ethan <31876119+qdgogogo@users.noreply.github.com>
|
||||||
|
Raghav Sood <raghavsood@gmail.com>
|
||||||
|
Ralph Caraveo <deckarep@gmail.com>
|
||||||
|
Ralph Caraveo III <deckarep@gmail.com>
|
||||||
|
Ramesh Nair <ram@hiddentao.com>
|
||||||
|
reinerRubin <tolstov.georgij@gmail.com>
|
||||||
|
rhaps107 <dod-source@yandex.ru>
|
||||||
|
Ricardo Catalinas Jiménez <r@untroubled.be>
|
||||||
|
Ricardo Domingos <ricardohsd@gmail.com>
|
||||||
|
Richard Hart <richardhart92@gmail.com>
|
||||||
|
RJ Catalano <catalanor0220@gmail.com>
|
||||||
|
Rob <robert@rojotek.com>
|
||||||
|
Rob Mulholand <rmulholand@8thlight.com>
|
||||||
|
Robert Zaremba <robert.zaremba@scale-it.pl>
|
||||||
|
Roc Yu <rociiu0112@gmail.com>
|
||||||
|
Runchao Han <elvisage941102@gmail.com>
|
||||||
|
Russ Cox <rsc@golang.org>
|
||||||
|
Ryan Schneider <ryanleeschneider@gmail.com>
|
||||||
|
Rémy Roy <remyroy@remyroy.com>
|
||||||
|
S. Matthew English <s-matthew-english@users.noreply.github.com>
|
||||||
|
salanfe <salanfe@users.noreply.github.com>
|
||||||
|
Samuel Marks <samuelmarks@gmail.com>
|
||||||
|
Sarlor <kinsleer@outlook.com>
|
||||||
|
Sasuke1964 <neilperry1964@gmail.com>
|
||||||
|
Saulius Grigaitis <saulius@necolt.com>
|
||||||
|
Sean <darcys22@gmail.com>
|
||||||
|
Sheldon <11510383@mail.sustc.edu.cn>
|
||||||
|
Sheldon <374662347@qq.com>
|
||||||
|
Shintaro Kaneko <kaneshin0120@gmail.com>
|
||||||
|
Shuai Qi <qishuai231@gmail.com>
|
||||||
|
Shunsuke Watanabe <ww.shunsuke@gmail.com>
|
||||||
|
silence <wangsai.silence@qq.com>
|
||||||
|
Simon Jentzsch <simon@slock.it>
|
||||||
|
slumber1122 <slumber1122@gmail.com>
|
||||||
|
Smilenator <yurivanenko@yandex.ru>
|
||||||
|
Sorin Neacsu <sorin.neacsu@gmail.com>
|
||||||
|
Stein Dekker <dekker.stein@gmail.com>
|
||||||
|
Steve Gattuso <steve@stevegattuso.me>
|
||||||
|
Steve Ruckdashel <steve.ruckdashel@gmail.com>
|
||||||
|
Steve Waldman <swaldman@mchange.com>
|
||||||
|
Steven Roose <stevenroose@gmail.com>
|
||||||
|
stompesi <stompesi@gmail.com>
|
||||||
|
stormpang <jialinpeng@vip.qq.com>
|
||||||
|
sunxiaojun2014 <sunxiaojun-xy@360.cn>
|
||||||
|
tamirms <tamir@trello.com>
|
||||||
|
Taylor Gerring <taylor.gerring@gmail.com>
|
||||||
|
TColl <38299499+TColl@users.noreply.github.com>
|
||||||
|
terasum <terasum@163.com>
|
||||||
|
Thomas Bocek <tom@tomp2p.net>
|
||||||
|
thomasmodeneis <thomas.modeneis@gmail.com>
|
||||||
|
thumb8432 <thumb8432@gmail.com>
|
||||||
|
Ti Zhou <tizhou1986@gmail.com>
|
||||||
|
Tosh Camille <tochecamille@gmail.com>
|
||||||
|
tsarpaul <Litvakpol@012.net.il>
|
||||||
|
tzapu <alex@tzapu.com>
|
||||||
|
ult-bobonovski <alex@ultiledger.io>
|
||||||
|
Valentin Wüstholz <wuestholz@gmail.com>
|
||||||
|
Vedhavyas Singareddi <vedhavyas.singareddi@gmail.com>
|
||||||
|
Victor Farazdagi <simple.square@gmail.com>
|
||||||
|
Victor Tran <vu.tran54@gmail.com>
|
||||||
|
Vie <yangchenzhong@gmail.com>
|
||||||
|
Viktor Trón <viktor.tron@gmail.com>
|
||||||
|
Ville Sundell <github@solarius.fi>
|
||||||
|
vim88 <vim88vim88@gmail.com>
|
||||||
|
Vincent G <caktux@gmail.com>
|
||||||
|
Vincent Serpoul <vincent@serpoul.com>
|
||||||
|
Vitalik Buterin <v@buterin.com>
|
||||||
|
Vitaly Bogdanov <vsbogd@gmail.com>
|
||||||
|
Vitaly V <vvelikodny@gmail.com>
|
||||||
|
Vivek Anand <vivekanand1101@users.noreply.github.com>
|
||||||
|
Vlad <gluk256@gmail.com>
|
||||||
|
Vlad Bokov <razum2um@mail.ru>
|
||||||
|
Vlad Gluhovsky <gluk256@users.noreply.github.com>
|
||||||
|
weimumu <934657014@qq.com>
|
||||||
|
Wenbiao Zheng <delweng@gmail.com>
|
||||||
|
William Setzer <bootstrapsetzer@gmail.com>
|
||||||
|
williambannas <wrschwartz@wpi.edu>
|
||||||
|
Wuxiang <wuxiangzhou2010@gmail.com>
|
||||||
|
xiekeyang <xiekeyang@users.noreply.github.com>
|
||||||
|
xincaosu <xincaosu@126.com>
|
||||||
|
yahtoo <yahtoo.ma@gmail.com>
|
||||||
|
YaoZengzeng <yaozengzeng@zju.edu.cn>
|
||||||
|
YH-Zhou <yanhong.zhou05@gmail.com>
|
||||||
|
Yohann Léon <sybiload@gmail.com>
|
||||||
|
Yoichi Hirai <i@yoichihirai.com>
|
||||||
|
Yondon Fu <yondon.fu@gmail.com>
|
||||||
|
YOSHIDA Masanori <masanori.yoshida@gmail.com>
|
||||||
|
yoza <yoza.is12s@gmail.com>
|
||||||
|
Yusup <awklsgrep@gmail.com>
|
||||||
|
Zach <zach.ramsay@gmail.com>
|
||||||
|
zah <zahary@gmail.com>
|
||||||
|
Zahoor Mohamed <zahoor@zahoor.in>
|
||||||
|
Zak Cole <zak@beattiecole.com>
|
||||||
|
zer0to0ne <36526113+zer0to0ne@users.noreply.github.com>
|
||||||
|
Zhenguo Niu <Niu.ZGlinux@gmail.com>
|
||||||
|
Zoe Nolan <github@zoenolan.org>
|
||||||
|
Zsolt Felföldi <zsfelfoldi@gmail.com>
|
||||||
|
Łukasz Kurowski <crackcomm@users.noreply.github.com>
|
||||||
|
ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com>
|
||||||
|
Максим Чусовлянов <mchusovlianov@gmail.com>
|
||||||
|
大彬 <hz_stb@163.com>
|
||||||
|
贺鹏飞 <hpf@hackerful.cn>
|
||||||
|
유용환 <33824408+eric-yoo@users.noreply.github.com>
|
||||||
|
67
CHANGELOG.md
67
CHANGELOG.md
@ -1,67 +0,0 @@
|
|||||||
## v0.4.3 (Unreleased)
|
|
||||||
|
|
||||||
### Notes
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
### Improvements
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
## v0.4.2 (28 June 2019)
|
|
||||||
|
|
||||||
### Notes
|
|
||||||
|
|
||||||
This release is not backward compatible with the previous versions of Swarm due to changes to the wire protocol of the Retrieve Request messages. Please update your nodes.
|
|
||||||
|
|
||||||
### Bug fixes and Improvements
|
|
||||||
|
|
||||||
* [#1503](https://github.com/ethersphere/swarm/pull/1503): network/simulation: add ExecAdapter capability to swarm simulations
|
|
||||||
* [#1495](https://github.com/ethersphere/swarm/pull/1495): build: enable ubuntu ppa disco (19.04) builds
|
|
||||||
* [#1395](https://github.com/ethersphere/swarm/pull/1395): swarm/storage: support for uploading 100gb files
|
|
||||||
* [#1344](https://github.com/ethersphere/swarm/pull/1344): swarm/network, swarm/storage: simplification of fetchers
|
|
||||||
* [#1488](https://github.com/ethersphere/swarm/pull/1488): docker: include git commit hash in swarm version
|
|
||||||
|
|
||||||
## v0.4.1 (June 13, 2019)
|
|
||||||
|
|
||||||
### Improvements
|
|
||||||
|
|
||||||
* [#1465](https://github.com/ethersphere/swarm/pull/1465): network: bump proto versions due to change in OfferedHashesMsg
|
|
||||||
* [#1428](https://github.com/ethersphere/swarm/pull/1428): swarm-smoke: add debug flag
|
|
||||||
* [#1422](https://github.com/ethersphere/swarm/pull/1422): swarm/network/stream: remove dead code
|
|
||||||
* [#1463](https://github.com/ethersphere/swarm/pull/1463): docker: create new dockerfiles that are context aware
|
|
||||||
* [#1466](https://github.com/ethersphere/swarm/pull/1466): changelog for releases
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
* [#1460](https://github.com/ethersphere/swarm/pull/1460): storage: fix alignement panics on 32 bit arch
|
|
||||||
* [#1422](https://github.com/ethersphere/swarm/pull/1422), [#19650](https://github.com/ethereum/go-ethereum/pull/19650): swarm/network/stream: remove dead code
|
|
||||||
* [#1420](https://github.com/ethersphere/swarm/pull/1420): swarm, cmd: fix migration link, change loglevel severity
|
|
||||||
* [#19594](https://github.com/ethereum/go-ethereum/pull/19594): swarm/api/http: fix bzz-hash to return ens resolved hash directly
|
|
||||||
* [#19599](https://github.com/ethereum/go-ethereum/pull/19599): swarm/storage: fix SubscribePull to not skip chunks
|
|
||||||
|
|
||||||
### Notes
|
|
||||||
|
|
||||||
* Swarm has split the codebase ([go-ethereum#19661](https://github.com/ethereum/go-ethereum/pull/19661), [#1405](https://github.com/ethersphere/swarm/pull/1405)) from [ethereum/go-ethereum](https://github.com/ethereum/go-ethereum). The code is now under [ethersphere/swarm](https://github.com/ethersphere/swarm)
|
|
||||||
* New docker images (>=0.4.0) can now be found under https://hub.docker.com/r/ethersphere/swarm
|
|
||||||
|
|
||||||
## v0.4.0 (May 17, 2019)
|
|
||||||
|
|
||||||
### Changes
|
|
||||||
|
|
||||||
* Implemented parallel feed lookups within Swarm Feeds
|
|
||||||
* Updated syncing protocol subscription algorithm
|
|
||||||
* Implemented EIP-1577 - Multiaddr support for ENS
|
|
||||||
* Improved LocalStore implementation
|
|
||||||
* Added support for syncing tags which provide the ability to measure how long it will take for an uploaded file to sync to the network
|
|
||||||
* Fixed data race bugs within PSS
|
|
||||||
* Improved end-to-end integration tests
|
|
||||||
* Various performance improvements and bug fixes
|
|
||||||
* Improved instrumentation - metrics and OpenTracing traces
|
|
||||||
|
|
||||||
### Notes
|
|
||||||
This release is not backward compatible with the previous versions of Swarm due to the new LocalStore implementation. If you wish to keep your data, you should run a data migration prior to running this version.
|
|
||||||
|
|
||||||
BZZ network ID has been updated to 4.
|
|
||||||
|
|
||||||
Swarm v0.4.0 introduces major changes to the existing codebase. Among other things, the storage layer has been rewritten to be more modular and flexible in a manner that will accommodate for our future needs. Since Swarm at this point does not provide any storage guarantees, we have made the decision to not impose any migrations on the nodes that we maintain as part of the public test network, nor on our users. We have provided a [manual](https://github.com/ethersphere/swarm/blob/master/docs/Migration-v0.3-to-v0.4.md) for those of you who are running private deployments and would like to migrate your data to the new local storage schema.
|
|
24
Dockerfile
24
Dockerfile
@ -1,14 +1,16 @@
|
|||||||
FROM golang:1.12-alpine as builder
|
# Build Geth in a stock Go builder container
|
||||||
|
FROM golang:1.13-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache make gcc musl-dev linux-headers git
|
RUN apk add --no-cache make gcc musl-dev linux-headers git
|
||||||
ADD . /swarm
|
|
||||||
WORKDIR /swarm
|
|
||||||
RUN make swarm
|
|
||||||
|
|
||||||
FROM ethereum/client-go:v1.8.27 as geth
|
ADD . /go-ethereum
|
||||||
|
RUN cd /go-ethereum && make geth
|
||||||
|
|
||||||
FROM alpine:3.9
|
# Pull Geth into a second stage deploy alpine container
|
||||||
RUN apk --no-cache add ca-certificates && update-ca-certificates
|
FROM alpine:latest
|
||||||
COPY --from=builder /swarm/build/bin/swarm /usr/local/bin/
|
|
||||||
COPY --from=geth /usr/local/bin/geth /usr/local/bin/
|
RUN apk add --no-cache ca-certificates
|
||||||
COPY docker/run.sh /run.sh
|
COPY --from=builder /go-ethereum/build/bin/geth /usr/local/bin/
|
||||||
ENTRYPOINT ["/run.sh"]
|
|
||||||
|
EXPOSE 8545 8546 8547 30303 30303/udp
|
||||||
|
ENTRYPOINT ["geth"]
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
FROM golang:1.12-alpine as builder
|
# Build Geth in a stock Go builder container
|
||||||
|
FROM golang:1.13-alpine as builder
|
||||||
|
|
||||||
RUN apk add --no-cache make gcc musl-dev linux-headers git
|
RUN apk add --no-cache make gcc musl-dev linux-headers git
|
||||||
ADD . /swarm
|
|
||||||
WORKDIR /swarm
|
|
||||||
RUN make alltools
|
|
||||||
|
|
||||||
FROM ethereum/client-go:v1.8.27 as geth
|
ADD . /go-ethereum
|
||||||
|
RUN cd /go-ethereum && make all
|
||||||
|
|
||||||
FROM alpine:3.9
|
# Pull all binaries into a second stage deploy alpine container
|
||||||
RUN apk --no-cache add ca-certificates
|
FROM alpine:latest
|
||||||
COPY --from=builder /swarm/build/bin/* /usr/local/bin/
|
|
||||||
COPY --from=geth /usr/local/bin/geth /usr/local/bin/
|
RUN apk add --no-cache ca-certificates
|
||||||
COPY docker/run.sh /run.sh
|
COPY --from=builder /go-ethereum/build/bin/* /usr/local/bin/
|
||||||
COPY docker/run-smoke.sh /run-smoke.sh
|
|
||||||
ENTRYPOINT ["/run.sh"]
|
EXPOSE 8545 8546 8547 30303 30303/udp
|
||||||
|
144
Makefile
144
Makefile
@ -2,12 +2,144 @@
|
|||||||
# with Go source code. If you know what GOPATH is then you probably
|
# with Go source code. If you know what GOPATH is then you probably
|
||||||
# don't need to bother with make.
|
# don't need to bother with make.
|
||||||
|
|
||||||
GOBIN = $(shell pwd)/build/bin
|
.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
|
||||||
|
.PHONY: geth-windows geth-windows-386 geth-windows-amd64
|
||||||
|
|
||||||
swarm:
|
GOBIN = ./build/bin
|
||||||
build/env.sh go run build/ci.go install ./cmd/swarm
|
GO ?= latest
|
||||||
|
|
||||||
|
geth:
|
||||||
|
build/env.sh go run build/ci.go install ./cmd/geth
|
||||||
@echo "Done building."
|
@echo "Done building."
|
||||||
@echo "Run \"$(GOBIN)/swarm\" to launch swarm."
|
@echo "Run \"$(GOBIN)/geth\" to launch geth."
|
||||||
|
|
||||||
alltools:
|
all:
|
||||||
build/env.sh go run build/ci.go install ./cmd/...
|
build/env.sh go run build/ci.go install
|
||||||
|
|
||||||
|
android:
|
||||||
|
build/env.sh go run build/ci.go aar --local
|
||||||
|
@echo "Done building."
|
||||||
|
@echo "Import \"$(GOBIN)/geth.aar\" to use the library."
|
||||||
|
|
||||||
|
ios:
|
||||||
|
build/env.sh go run build/ci.go xcode --local
|
||||||
|
@echo "Done building."
|
||||||
|
@echo "Import \"$(GOBIN)/Geth.framework\" to use the library."
|
||||||
|
|
||||||
|
test: all
|
||||||
|
build/env.sh go run build/ci.go test
|
||||||
|
|
||||||
|
lint: ## Run linters.
|
||||||
|
build/env.sh go run build/ci.go lint
|
||||||
|
|
||||||
|
clean:
|
||||||
|
go clean -cache
|
||||||
|
rm -fr build/_workspace/pkg/ $(GOBIN)/*
|
||||||
|
|
||||||
|
# The devtools target installs tools required for 'go generate'.
|
||||||
|
# You need to put $GOBIN (or $GOPATH/bin) in your PATH to use 'go generate'.
|
||||||
|
|
||||||
|
devtools:
|
||||||
|
env GOBIN= go get -u golang.org/x/tools/cmd/stringer
|
||||||
|
env GOBIN= go get -u github.com/kevinburke/go-bindata/go-bindata
|
||||||
|
env GOBIN= go get -u github.com/fjl/gencodec
|
||||||
|
env GOBIN= go get -u github.com/golang/protobuf/protoc-gen-go
|
||||||
|
env GOBIN= go install ./cmd/abigen
|
||||||
|
@type "npm" 2> /dev/null || echo 'Please install node.js and npm'
|
||||||
|
@type "solc" 2> /dev/null || echo 'Please install solc'
|
||||||
|
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
||||||
|
|
||||||
|
# Cross Compilation Targets (xgo)
|
||||||
|
|
||||||
|
geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios
|
||||||
|
@echo "Full cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-*
|
||||||
|
|
||||||
|
geth-linux: geth-linux-386 geth-linux-amd64 geth-linux-arm geth-linux-mips64 geth-linux-mips64le
|
||||||
|
@echo "Linux cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-*
|
||||||
|
|
||||||
|
geth-linux-386:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/386 -v ./cmd/geth
|
||||||
|
@echo "Linux 386 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep 386
|
||||||
|
|
||||||
|
geth-linux-amd64:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/amd64 -v ./cmd/geth
|
||||||
|
@echo "Linux amd64 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep amd64
|
||||||
|
|
||||||
|
geth-linux-arm: geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64
|
||||||
|
@echo "Linux ARM cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep arm
|
||||||
|
|
||||||
|
geth-linux-arm-5:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm-5 -v ./cmd/geth
|
||||||
|
@echo "Linux ARMv5 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep arm-5
|
||||||
|
|
||||||
|
geth-linux-arm-6:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm-6 -v ./cmd/geth
|
||||||
|
@echo "Linux ARMv6 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep arm-6
|
||||||
|
|
||||||
|
geth-linux-arm-7:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm-7 -v ./cmd/geth
|
||||||
|
@echo "Linux ARMv7 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep arm-7
|
||||||
|
|
||||||
|
geth-linux-arm64:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/arm64 -v ./cmd/geth
|
||||||
|
@echo "Linux ARM64 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep arm64
|
||||||
|
|
||||||
|
geth-linux-mips:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips --ldflags '-extldflags "-static"' -v ./cmd/geth
|
||||||
|
@echo "Linux MIPS cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep mips
|
||||||
|
|
||||||
|
geth-linux-mipsle:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mipsle --ldflags '-extldflags "-static"' -v ./cmd/geth
|
||||||
|
@echo "Linux MIPSle cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep mipsle
|
||||||
|
|
||||||
|
geth-linux-mips64:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips64 --ldflags '-extldflags "-static"' -v ./cmd/geth
|
||||||
|
@echo "Linux MIPS64 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep mips64
|
||||||
|
|
||||||
|
geth-linux-mips64le:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips64le --ldflags '-extldflags "-static"' -v ./cmd/geth
|
||||||
|
@echo "Linux MIPS64le cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-linux-* | grep mips64le
|
||||||
|
|
||||||
|
geth-darwin: geth-darwin-386 geth-darwin-amd64
|
||||||
|
@echo "Darwin cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-darwin-*
|
||||||
|
|
||||||
|
geth-darwin-386:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=darwin/386 -v ./cmd/geth
|
||||||
|
@echo "Darwin 386 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-darwin-* | grep 386
|
||||||
|
|
||||||
|
geth-darwin-amd64:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=darwin/amd64 -v ./cmd/geth
|
||||||
|
@echo "Darwin amd64 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-darwin-* | grep amd64
|
||||||
|
|
||||||
|
geth-windows: geth-windows-386 geth-windows-amd64
|
||||||
|
@echo "Windows cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-windows-*
|
||||||
|
|
||||||
|
geth-windows-386:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=windows/386 -v ./cmd/geth
|
||||||
|
@echo "Windows 386 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-windows-* | grep 386
|
||||||
|
|
||||||
|
geth-windows-amd64:
|
||||||
|
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=windows/amd64 -v ./cmd/geth
|
||||||
|
@echo "Windows amd64 cross compilation done:"
|
||||||
|
@ls -ld $(GOBIN)/geth-windows-* | grep amd64
|
||||||
|
25
OWNERS
25
OWNERS
@ -1,25 +0,0 @@
|
|||||||
# Ownership by go packages
|
|
||||||
|
|
||||||
swarm
|
|
||||||
├── api ─────────────────── ethersphere
|
|
||||||
├── bmt ─────────────────── @zelig
|
|
||||||
├── dev ─────────────────── @lmars
|
|
||||||
├── fuse ────────────────── @jmozah, @holisticode
|
|
||||||
├── grafana_dashboards ──── @nonsense
|
|
||||||
├── metrics ─────────────── @nonsense, @holisticode
|
|
||||||
├── network ─────────────── ethersphere
|
|
||||||
│ ├── bitvector ───────── @zelig, @janos, @gbalint
|
|
||||||
│ ├── priorityqueue ───── @zelig, @janos, @gbalint
|
|
||||||
│ ├── simulations ─────── @zelig
|
|
||||||
│ └── stream ──────────── @janos, @zelig, @gbalint, @holisticode, @justelad
|
|
||||||
│ ├── intervals ───── @janos
|
|
||||||
│ └── testing ─────── @zelig
|
|
||||||
├── pot ─────────────────── @zelig
|
|
||||||
├── pss ─────────────────── @nolash, @zelig, @nonsense
|
|
||||||
├── services ────────────── @zelig
|
|
||||||
├── state ───────────────── @justelad
|
|
||||||
├── storage ─────────────── ethersphere
|
|
||||||
│ ├── encryption ──────── @gbalint, @zelig, @nagydani
|
|
||||||
│ ├── mock ────────────── @janos
|
|
||||||
│ └── feed ────────────── @nolash, @jpeletier
|
|
||||||
└── testutil ────────────── @lmars
|
|
547
README.md
547
README.md
@ -1,345 +1,348 @@
|
|||||||
## Swarm <!-- omit in toc -->
|
## Go Ethereum
|
||||||
|
|
||||||
[https://swarm.ethereum.org](https://swarm.ethereum.org)
|
Official Golang implementation of the Ethereum protocol.
|
||||||
|
|
||||||
Swarm is a distributed storage platform and content distribution service, a native base layer service of the ethereum web3 stack. The primary objective of Swarm is to provide a decentralized and redundant store for dapp code and data as well as block chain and state data. Swarm is also set out to provide various base layer services for web3, including node-to-node messaging, media streaming, decentralised database services and scalable state-channel infrastructure for decentralised service economies.
|
[](https://godoc.org/github.com/ethereum/go-ethereum)
|
||||||
|
[](https://goreportcard.com/report/github.com/ethereum/go-ethereum)
|
||||||
|
[](https://travis-ci.org/ethereum/go-ethereum)
|
||||||
|
[](https://discord.gg/nthXNEv)
|
||||||
|
|
||||||
[](https://travis-ci.org/ethersphere/swarm)
|
Automated builds are available for stable releases and the unstable master branch. Binary
|
||||||
[](https://gitter.im/ethersphere/orange-lounge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
archives are published at https://geth.ethereum.org/downloads/.
|
||||||
|
|
||||||
## Table of Contents <!-- omit in toc -->
|
|
||||||
|
|
||||||
- [Building the source](#building-the-source)
|
|
||||||
- [Running Swarm](#running-swarm)
|
|
||||||
- [Verifying that your local Swarm node is running](#verifying-that-your-local-swarm-node-is-running)
|
|
||||||
- [Ethereum Name Service resolution](#ethereum-name-service-resolution)
|
|
||||||
- [Documentation](#documentation)
|
|
||||||
- [Docker](#docker)
|
|
||||||
- [Docker tags](#docker-tags)
|
|
||||||
- [Environment variables](#environment-variables)
|
|
||||||
- [Swarm command line arguments](#swarm-command-line-arguments)
|
|
||||||
- [Developers Guide](#developers-guide)
|
|
||||||
- [Go Environment](#go-environment)
|
|
||||||
- [Vendored Dependencies](#vendored-dependencies)
|
|
||||||
- [Testing](#testing)
|
|
||||||
- [Profiling Swarm](#profiling-swarm)
|
|
||||||
- [Metrics and Instrumentation in Swarm](#metrics-and-instrumentation-in-swarm)
|
|
||||||
- [Visualizing metrics](#visualizing-metrics)
|
|
||||||
- [Public Gateways](#public-gateways)
|
|
||||||
- [Swarm Dapps](#swarm-dapps)
|
|
||||||
- [Contributing](#contributing)
|
|
||||||
- [License](#license)
|
|
||||||
|
|
||||||
## Building the source
|
## Building the source
|
||||||
|
|
||||||
Building Swarm requires Go (version 1.11 or later).
|
For prerequisites and detailed build instructions please read the [Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) on the wiki.
|
||||||
|
|
||||||
To simply compile the `swarm` binary without a `GOPATH`:
|
Building `geth` requires both a Go (version 1.10 or later) and a C compiler. You can install
|
||||||
|
them using your favourite package manager. Once the dependencies are installed, run
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
$ git clone https://github.com/ethersphere/swarm
|
make geth
|
||||||
$ cd swarm
|
|
||||||
$ make swarm
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You will find the binary under `./build/bin/swarm`.
|
or, to build the full suite of utilities:
|
||||||
|
|
||||||
To build a vendored `swarm` using `go get` you must have `GOPATH` set. Then run:
|
```shell
|
||||||
|
make all
|
||||||
```bash
|
|
||||||
$ go get -d github.com/ethersphere/swarm
|
|
||||||
$ go install github.com/ethersphere/swarm/cmd/swarm
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running Swarm
|
## Executables
|
||||||
|
|
||||||
Going through all the possible command line flags is out of scope here, but we've enumerated a few common parameter combos to get you up to speed quickly on how you can run your own Swarm node.
|
The go-ethereum project comes with several wrappers/executables found in the `cmd`
|
||||||
|
directory.
|
||||||
|
|
||||||
To run Swarm you need an Ethereum account. Download and install [Geth](https://geth.ethereum.org) if you don't have it on your system. You can create a new Ethereum account by running the following command:
|
| 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. |
|
||||||
|
|
||||||
```bash
|
## Running `geth`
|
||||||
$ geth account new
|
|
||||||
|
Going through all the possible command line flags is out of scope here (please consult our
|
||||||
|
[CLI Wiki page](https://github.com/ethereum/go-ethereum/wiki/Command-Line-Options)),
|
||||||
|
but we've enumerated a few common parameter combos to get you up to speed quickly
|
||||||
|
on how you can run your own `geth` instance.
|
||||||
|
|
||||||
|
### Full node on the main Ethereum network
|
||||||
|
|
||||||
|
By far the most common scenario is people wanting to simply interact with the Ethereum
|
||||||
|
network: create accounts; transfer funds; deploy and interact with contracts. For this
|
||||||
|
particular use-case the user doesn't care about years-old historical data, so we can
|
||||||
|
fast-sync quickly to the current state of the network. To do so:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ geth console
|
||||||
```
|
```
|
||||||
|
|
||||||
You will be prompted for a password:
|
This command will:
|
||||||
|
* Start `geth` in fast sync mode (default, can be changed with the `--syncmode` flag),
|
||||||
|
causing it to download more data in exchange for avoiding processing the entire history
|
||||||
|
of the Ethereum network, which is very CPU intensive.
|
||||||
|
* Start up `geth`'s built-in interactive [JavaScript console](https://github.com/ethereum/go-ethereum/wiki/JavaScript-Console),
|
||||||
|
(via the trailing `console` subcommand) through which you can invoke all official [`web3` methods](https://github.com/ethereum/wiki/wiki/JavaScript-API)
|
||||||
|
as well as `geth`'s own [management APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs).
|
||||||
|
This tool is optional and if you leave it out you can always attach to an already running
|
||||||
|
`geth` instance with `geth attach`.
|
||||||
|
|
||||||
```
|
### A Full node on the Ethereum test network
|
||||||
Your new account is locked with a password. Please give a password. Do not forget this password.
|
|
||||||
Passphrase:
|
Transitioning towards developers, if you'd like to play around with creating Ethereum
|
||||||
Repeat passphrase:
|
contracts, you almost certainly would like to do that without any real money involved until
|
||||||
|
you get the hang of the entire system. In other words, instead of attaching to the main
|
||||||
|
network, you want to join the **test** network with your node, which is fully equivalent to
|
||||||
|
the main network, but with play-Ether only.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ geth --testnet console
|
||||||
```
|
```
|
||||||
|
|
||||||
Once you have specified the password, the output will be the Ethereum address representing that account. For example:
|
The `console` subcommand has the exact same meaning as above and they are equally
|
||||||
|
useful on the testnet too. Please see above for their explanations if you've skipped here.
|
||||||
|
|
||||||
```
|
Specifying the `--testnet` flag, however, will reconfigure your `geth` instance a bit:
|
||||||
Address: {2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1}
|
|
||||||
|
* Instead of using the default data directory (`~/.ethereum` on Linux for example), `geth`
|
||||||
|
will nest itself one level deeper into a `testnet` subfolder (`~/.ethereum/testnet` on
|
||||||
|
Linux). Note, on OSX and Linux this also means that attaching to a running testnet node
|
||||||
|
requires the use of a custom endpoint since `geth attach` will try to attach to a
|
||||||
|
production node endpoint by default. E.g.
|
||||||
|
`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 separate the two networks and will not make any
|
||||||
|
accounts available between them.*
|
||||||
|
|
||||||
|
### Full node on the Rinkeby test network
|
||||||
|
|
||||||
|
The above test network is a cross-client one based on the ethash proof-of-work consensus
|
||||||
|
algorithm. As such, it has certain extra overhead and is more susceptible to reorganization
|
||||||
|
attacks due to the network's low difficulty/security. Go Ethereum also supports connecting
|
||||||
|
to a proof-of-authority based test network called [*Rinkeby*](https://www.rinkeby.io)
|
||||||
|
(operated by members of the community). This network is lighter, more secure, but is only
|
||||||
|
supported by go-ethereum.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ geth --rinkeby console
|
||||||
```
|
```
|
||||||
|
|
||||||
Using this account, connect to Swarm with
|
### Configuration
|
||||||
|
|
||||||
```bash
|
As an alternative to passing the numerous flags to the `geth` binary, you can also pass a
|
||||||
$ swarm --bzzaccount <your-account-here>
|
configuration file via:
|
||||||
|
|
||||||
# in our example
|
```shell
|
||||||
$ swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1
|
$ geth --config /path/to/your_config.toml
|
||||||
```
|
```
|
||||||
|
|
||||||
### Verifying that your local Swarm node is running
|
To get an idea how the file should look like you can use the `dumpconfig` subcommand to
|
||||||
|
export your existing configuration:
|
||||||
|
|
||||||
When running, Swarm is accessible through an HTTP API on port 8500.
|
```shell
|
||||||
|
$ geth --your-favourite-flags dumpconfig
|
||||||
Confirm that it is up and running by pointing your browser to http://localhost:8500
|
|
||||||
|
|
||||||
### Ethereum Name Service resolution
|
|
||||||
|
|
||||||
The Ethereum Name Service is the Ethereum equivalent of DNS in the classic web. In order to use ENS to resolve names to Swarm content hashes (e.g. `bzz://theswarm.eth`), `swarm` has to connect to a `geth` instance, which is synced with the Ethereum mainnet. This is done using the `--ens-api` flag.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ swarm --bzzaccount <your-account-here> \
|
|
||||||
--ens-api '$HOME/.ethereum/geth.ipc'
|
|
||||||
|
|
||||||
# in our example
|
|
||||||
$ swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1 \
|
|
||||||
--ens-api '$HOME/.ethereum/geth.ipc'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information on usage, features or command line flags, please consult the Documentation.
|
*Note: This works only with `geth` v1.6.0 and above.*
|
||||||
|
|
||||||
## Documentation
|
#### Docker quick start
|
||||||
|
|
||||||
Swarm documentation can be found at [https://swarm-guide.readthedocs.io](https://swarm-guide.readthedocs.io).
|
One of the quickest ways to get Ethereum up and running on your machine is by using
|
||||||
|
Docker:
|
||||||
|
|
||||||
## Docker
|
```shell
|
||||||
|
docker run -d --name ethereum-node -v /Users/alice/ethereum:/root \
|
||||||
Swarm container images are available at Docker Hub: [ethersphere/swarm](https://hub.docker.com/r/ethersphere/swarm)
|
-p 8545:8545 -p 30303:30303 \
|
||||||
|
ethereum/client-go
|
||||||
### Docker tags
|
|
||||||
|
|
||||||
* `latest` - latest stable release
|
|
||||||
* `edge` - latest build from `master`
|
|
||||||
* `v0.x.y` - specific stable release
|
|
||||||
|
|
||||||
### Environment variables
|
|
||||||
|
|
||||||
* `PASSWORD` - *required* - Used to setup a sample Ethereum account in the data directory. If a data directory is mounted with a volume, the first Ethereum account from it is loaded, and Swarm will try to decrypt it non-interactively with `PASSWORD`
|
|
||||||
* `DATADIR` - *optional* - Defaults to `/root/.ethereum`
|
|
||||||
|
|
||||||
### Swarm command line arguments
|
|
||||||
|
|
||||||
All Swarm command line arguments are supported and can be sent as part of the CMD field to the Docker container.
|
|
||||||
|
|
||||||
**Examples:**
|
|
||||||
|
|
||||||
Running a Swarm container from the command line
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
|
||||||
--debug \
|
|
||||||
--verbosity 4
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Running a Swarm container with custom ENS endpoint
|
This will start `geth` in fast-sync mode with a DB memory allowance of 1GB just as the
|
||||||
|
above command does. It will also create a persistent volume in your home directory for
|
||||||
|
saving your blockchain as well as map the default ports. There is also an `alpine` tag
|
||||||
|
available for a slim version of the image.
|
||||||
|
|
||||||
```bash
|
Do not forget `--rpcaddr 0.0.0.0`, if you want to access RPC from other containers
|
||||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
and/or hosts. By default, `geth` binds to the local interface and RPC endpoints is not
|
||||||
--ens-api http://1.2.3.4:8545 \
|
accessible from the outside.
|
||||||
--debug \
|
|
||||||
--verbosity 4
|
### Programmatically interfacing `geth` nodes
|
||||||
|
|
||||||
|
As a developer, sooner rather than later you'll want to start interacting with `geth` and the
|
||||||
|
Ethereum network via your own programs and not manually through the console. To aid
|
||||||
|
this, `geth` has built-in support for a JSON-RPC based APIs ([standard APIs](https://github.com/ethereum/wiki/wiki/JSON-RPC)
|
||||||
|
and [`geth` specific APIs](https://github.com/ethereum/go-ethereum/wiki/Management-APIs)).
|
||||||
|
These can be exposed via HTTP, WebSockets and IPC (UNIX sockets on UNIX based
|
||||||
|
platforms, and named pipes on Windows).
|
||||||
|
|
||||||
|
The IPC interface is enabled by default and exposes all the APIs supported by `geth`,
|
||||||
|
whereas the HTTP and WS interfaces need to manually be enabled and only expose a
|
||||||
|
subset of APIs due to security reasons. These can be turned on/off and configured as
|
||||||
|
you'd expect.
|
||||||
|
|
||||||
|
HTTP based JSON-RPC API options:
|
||||||
|
|
||||||
|
* `--rpc` Enable the HTTP-RPC server
|
||||||
|
* `--rpcaddr` HTTP-RPC server listening interface (default: `localhost`)
|
||||||
|
* `--rpcport` HTTP-RPC server listening port (default: `8545`)
|
||||||
|
* `--rpcapi` API's offered over the HTTP-RPC interface (default: `eth,net,web3`)
|
||||||
|
* `--rpccorsdomain` Comma separated list of domains from which to accept cross origin requests (browser enforced)
|
||||||
|
* `--ws` Enable the WS-RPC server
|
||||||
|
* `--wsaddr` WS-RPC server listening interface (default: `localhost`)
|
||||||
|
* `--wsport` WS-RPC server listening port (default: `8546`)
|
||||||
|
* `--wsapi` API's offered over the WS-RPC interface (default: `eth,net,web3`)
|
||||||
|
* `--wsorigins` Origins from which to accept websockets requests
|
||||||
|
* `--ipcdisable` Disable the IPC-RPC server
|
||||||
|
* `--ipcapi` API's offered over the IPC-RPC interface (default: `admin,debug,eth,miner,net,personal,shh,txpool,web3`)
|
||||||
|
* `--ipcpath` Filename for IPC socket/pipe within the datadir (explicit paths escape it)
|
||||||
|
|
||||||
|
You'll need to use your own programming environments' capabilities (libraries, tools, etc) to
|
||||||
|
connect via HTTP, WS or IPC to a `geth` node configured with the above flags and you'll
|
||||||
|
need to speak [JSON-RPC](https://www.jsonrpc.org/specification) on all transports. You
|
||||||
|
can reuse the same connection for multiple requests!
|
||||||
|
|
||||||
|
**Note: Please understand the security implications of opening up an HTTP/WS based
|
||||||
|
transport before doing so! Hackers on the internet are actively trying to subvert
|
||||||
|
Ethereum nodes with exposed APIs! Further, all browser tabs can access locally
|
||||||
|
running web servers, so malicious web pages could try to subvert locally available
|
||||||
|
APIs!**
|
||||||
|
|
||||||
|
### Operating a private network
|
||||||
|
|
||||||
|
Maintaining your own private network is more involved as a lot of configurations taken for
|
||||||
|
granted in the official networks need to be manually set up.
|
||||||
|
|
||||||
|
#### Defining the private genesis state
|
||||||
|
|
||||||
|
First, you'll need to create the genesis state of your networks, which all nodes need to be
|
||||||
|
aware of and agree upon. This consists of a small JSON file (e.g. call it `genesis.json`):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"chainId": <arbitrary positive integer>,
|
||||||
|
"homesteadBlock": 0,
|
||||||
|
"eip150Block": 0,
|
||||||
|
"eip155Block": 0,
|
||||||
|
"eip158Block": 0,
|
||||||
|
"byzantiumBlock": 0,
|
||||||
|
"constantinopleBlock": 0,
|
||||||
|
"petersburgBlock": 0
|
||||||
|
},
|
||||||
|
"alloc": {},
|
||||||
|
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||||
|
"difficulty": "0x20000",
|
||||||
|
"extraData": "",
|
||||||
|
"gasLimit": "0x2fefd8",
|
||||||
|
"nonce": "0x0000000000000042",
|
||||||
|
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"timestamp": "0x00"
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Running a Swarm container with metrics enabled
|
The above fields should be fine for most purposes, although we'd recommend changing
|
||||||
|
the `nonce` to some random value so you prevent unknown remote nodes from being able
|
||||||
|
to connect to you. If you'd like to pre-fund some accounts for easier testing, create
|
||||||
|
the accounts and populate the `alloc` field with their addresses.
|
||||||
|
|
||||||
```bash
|
```json
|
||||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
"alloc": {
|
||||||
--debug \
|
"0x0000000000000000000000000000000000000001": {
|
||||||
--metrics \
|
"balance": "111111111"
|
||||||
--metrics.influxdb.export \
|
},
|
||||||
--metrics.influxdb.endpoint "http://localhost:8086" \
|
"0x0000000000000000000000000000000000000002": {
|
||||||
--metrics.influxdb.username "user" \
|
"balance": "222222222"
|
||||||
--metrics.influxdb.password "pass" \
|
}
|
||||||
--metrics.influxdb.database "metrics" \
|
}
|
||||||
--metrics.influxdb.host.tag "localhost" \
|
|
||||||
--verbosity 4
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Running a Swarm container with tracing and pprof server enabled
|
With the genesis state defined in the above JSON file, you'll need to initialize **every**
|
||||||
|
`geth` node with it prior to starting it up to ensure all blockchain parameters are correctly
|
||||||
|
set:
|
||||||
|
|
||||||
```bash
|
```shell
|
||||||
$ docker run -e PASSWORD=password123 -t ethersphere/swarm \
|
$ geth init path/to/genesis.json
|
||||||
--debug \
|
|
||||||
--tracing \
|
|
||||||
--tracing.endpoint 127.0.0.1:6831 \
|
|
||||||
--tracing.svc myswarm \
|
|
||||||
--pprof \
|
|
||||||
--pprofaddr 0.0.0.0 \
|
|
||||||
--pprofport 6060
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Running a Swarm container with custom data directory mounted from a volume
|
#### Creating the rendezvous point
|
||||||
|
|
||||||
```bash
|
With all nodes that you want to run initialized to the desired genesis state, you'll need to
|
||||||
$ docker run -e DATADIR=/data -e PASSWORD=password123 -v /tmp/hostdata:/data -t ethersphere/swarm \
|
start a bootstrap node that others can use to find each other in your network and/or over
|
||||||
--debug \
|
the internet. The clean way is to configure and run a dedicated bootnode:
|
||||||
--verbosity 4
|
|
||||||
|
```shell
|
||||||
|
$ bootnode --genkey=boot.key
|
||||||
|
$ bootnode --nodekey=boot.key
|
||||||
```
|
```
|
||||||
|
|
||||||
## Developers Guide
|
With the bootnode online, it will display an [`enode` URL](https://github.com/ethereum/wiki/wiki/enode-url-format)
|
||||||
|
that other nodes can use to connect to it and exchange peer information. Make sure to
|
||||||
|
replace the displayed IP address information (most probably `[::]`) with your externally
|
||||||
|
accessible IP to get the actual `enode` URL.
|
||||||
|
|
||||||
### Go Environment
|
*Note: You could also use a full-fledged `geth` node as a bootnode, but it's the less
|
||||||
|
recommended way.*
|
||||||
|
|
||||||
We assume that you have Go v1.11 installed, and `GOPATH` is set.
|
#### Starting up your member nodes
|
||||||
|
|
||||||
You must have your working copy under `$GOPATH/src/github.com/ethersphere/swarm`.
|
With the bootnode operational and externally reachable (you can try
|
||||||
|
`telnet <ip> <port>` to ensure it's indeed reachable), start every subsequent `geth`
|
||||||
|
node pointed to the bootnode for peer discovery via the `--bootnodes` flag. It will
|
||||||
|
probably also be desirable to keep the data directory of your private network separated, so
|
||||||
|
do also specify a custom `--datadir` flag.
|
||||||
|
|
||||||
Most likely you will be working from your fork of `swarm`, let's say from `github.com/nirname/swarm`. Clone or move your fork into the right place:
|
```shell
|
||||||
|
$ geth --datadir=path/to/custom/data/folder --bootnodes=<bootnode-enode-url-from-above>
|
||||||
```bash
|
|
||||||
$ git clone git@github.com:nirname/swarm.git $GOPATH/src/github.com/ethersphere/swarm
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
*Note: Since your network will be completely cut off from the main and test networks, you'll
|
||||||
|
also need to configure a miner to process transactions and create new blocks for you.*
|
||||||
|
|
||||||
### Vendored Dependencies
|
#### Running a private miner
|
||||||
|
|
||||||
All dependencies are tracked in the `vendor` directory. We use `govendor` to manage them.
|
Mining on the public Ethereum network is a complex task as it's only feasible using GPUs,
|
||||||
|
requiring an OpenCL or CUDA enabled `ethminer` instance. For information on such a
|
||||||
|
setup, please consult the [EtherMining subreddit](https://www.reddit.com/r/EtherMining/)
|
||||||
|
and the [Genoil miner](https://github.com/Genoil/cpp-ethereum) repository.
|
||||||
|
|
||||||
If you want to add a new dependency, run `govendor fetch <import-path>`, then commit the result.
|
In a private network setting, however a single CPU miner instance is more than enough for
|
||||||
|
practical purposes as it can produce a stable stream of blocks at the correct intervals
|
||||||
|
without needing heavy resources (consider running on a single thread, no need for multiple
|
||||||
|
ones either). To start a `geth` instance for mining, run it with all your usual flags, extended
|
||||||
|
by:
|
||||||
|
|
||||||
If you want to update all dependencies to their latest upstream version, run `govendor fetch +v`.
|
```shell
|
||||||
|
$ geth <usual-flags> --mine --miner.threads=1 --etherbase=0x0000000000000000000000000000000000000000
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
This section explains how to run unit, integration, and end-to-end tests in your development sandbox.
|
|
||||||
|
|
||||||
Testing one library:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ go test -v -cpu 4 ./api
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Using options -cpu (number of cores allowed) and -v (logging even if no error) is recommended.
|
Which will start mining blocks and transactions on a single CPU thread, crediting all
|
||||||
|
proceedings to the account specified by `--etherbase`. You can further tune the mining
|
||||||
|
by changing the default gas limit blocks converge to (`--targetgaslimit`) and the price
|
||||||
|
transactions are accepted at (`--gasprice`).
|
||||||
|
|
||||||
Testing only some methods:
|
## Contribution
|
||||||
|
|
||||||
```bash
|
Thank you for considering to help out with the source code! We welcome contributions
|
||||||
$ go test -v -cpu 4 ./api -run TestMethod
|
from anyone on the internet, and are grateful for even the smallest of fixes!
|
||||||
```
|
|
||||||
|
|
||||||
Note: here all tests with prefix TestMethod will be run, so if you got TestMethod, TestMethod1, then both!
|
If you'd like to contribute to go-ethereum, please fork, fix, commit and send a pull request
|
||||||
|
for the maintainers to review and merge into the main code base. If you wish to submit
|
||||||
Running benchmarks:
|
more complex changes though, please check up with the core devs first on [our gitter channel](https://gitter.im/ethereum/go-ethereum)
|
||||||
|
to ensure those changes are in line with the general philosophy of the project and/or get
|
||||||
```bash
|
some early feedback which can make both your efforts much lighter as well as our review
|
||||||
$ go test -v -cpu 4 -bench . -run BenchmarkJoin
|
and merge procedures quick and simple.
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Profiling Swarm
|
|
||||||
|
|
||||||
This section explains how to add Go `pprof` profiler to Swarm
|
|
||||||
|
|
||||||
If `swarm` is started with the `--pprof` option, a debugging HTTP server is made available on port 6060.
|
|
||||||
|
|
||||||
You can bring up http://localhost:6060/debug/pprof to see the heap, running routines etc.
|
|
||||||
|
|
||||||
By clicking full goroutine stack dump (clicking http://localhost:6060/debug/pprof/goroutine?debug=2) you can generate trace that is useful for debugging.
|
|
||||||
|
|
||||||
|
|
||||||
### Metrics and Instrumentation in Swarm
|
|
||||||
|
|
||||||
This section explains how to visualize and use existing Swarm metrics and how to instrument Swarm with a new metric.
|
|
||||||
|
|
||||||
Swarm metrics system is based on the `go-metrics` library.
|
|
||||||
|
|
||||||
The most common types of measurements we use in Swarm are `counters` and `resetting timers`. Consult the `go-metrics` documentation for full reference of available types.
|
|
||||||
|
|
||||||
```go
|
|
||||||
// incrementing a counter
|
|
||||||
metrics.GetOrRegisterCounter("network.stream.received_chunks", nil).Inc(1)
|
|
||||||
|
|
||||||
// measuring latency with a resetting timer
|
|
||||||
start := time.Now()
|
|
||||||
t := metrics.GetOrRegisterResettingTimer("http.request.GET.time"), nil)
|
|
||||||
...
|
|
||||||
t := UpdateSince(start)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Visualizing metrics
|
|
||||||
|
|
||||||
Swarm supports an InfluxDB exporter. Consult the help section to learn about the command line arguments used to configure it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ swarm --help | grep metrics
|
|
||||||
```
|
|
||||||
|
|
||||||
We use Grafana and InfluxDB to visualise metrics reported by Swarm. We keep our Grafana dashboards under version control at https://github.com/ethersphere/grafana-dashboards. You could use them or design your own.
|
|
||||||
|
|
||||||
We have built a tool to help with automatic start of Grafana and InfluxDB and provisioning of dashboards at https://github.com/nonsense/stateth, which requires that you have Docker installed.
|
|
||||||
|
|
||||||
Once you have `stateth` installed, and you have Docker running locally, you have to:
|
|
||||||
|
|
||||||
1. Run `stateth` and keep it running in the background
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ stateth --rm --grafana-dashboards-folder $GOPATH/src/github.com/ethersphere/grafana-dashboards --influxdb-database metrics
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Run `swarm` with at least the following params:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
--metrics \
|
|
||||||
--metrics.influxdb.export \
|
|
||||||
--metrics.influxdb.endpoint "http://localhost:8086" \
|
|
||||||
--metrics.influxdb.username "admin" \
|
|
||||||
--metrics.influxdb.password "admin" \
|
|
||||||
--metrics.influxdb.database "metrics"
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Open Grafana at http://localhost:3000 and view the dashboards to gain insight into Swarm.
|
|
||||||
|
|
||||||
|
|
||||||
## Public Gateways
|
|
||||||
|
|
||||||
Swarm offers a local HTTP proxy API that Dapps can use to interact with Swarm. The Ethereum Foundation is hosting a public gateway, which allows free access so that people can try Swarm without running their own node.
|
|
||||||
|
|
||||||
The Swarm public gateways are temporary and users should not rely on their existence for production services.
|
|
||||||
|
|
||||||
The Swarm public gateway can be found at https://swarm-gateways.net and is always running the latest `stable` Swarm release.
|
|
||||||
|
|
||||||
## Swarm Dapps
|
|
||||||
|
|
||||||
You can find a few reference Swarm decentralised applications at: https://swarm-gateways.net/bzz:/swarmapps.eth
|
|
||||||
|
|
||||||
Their source code can be found at: https://github.com/ethersphere/swarm-dapps
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Thank you for considering to help out with the source code! We welcome contributions from
|
|
||||||
anyone on the internet, and are grateful for even the smallest of fixes!
|
|
||||||
|
|
||||||
If you'd like to contribute to Swarm, please fork, fix, commit and send a pull request
|
|
||||||
for the maintainers to review and merge into the main code base. If you wish to submit more
|
|
||||||
complex changes though, please check up with the core devs first on [our Swarm gitter channel](https://gitter.im/ethersphere/orange-lounge)
|
|
||||||
to ensure those changes are in line with the general philosophy of the project and/or get some
|
|
||||||
early feedback which can make both your efforts much lighter as well as our review and merge
|
|
||||||
procedures quick and simple.
|
|
||||||
|
|
||||||
Please make sure your contributions adhere to our coding guidelines:
|
Please make sure your contributions adhere to our coding guidelines:
|
||||||
|
|
||||||
* Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
* Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting)
|
||||||
* Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
|
guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
|
||||||
|
* Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary)
|
||||||
|
guidelines.
|
||||||
* Pull requests need to be based on and opened against the `master` branch.
|
* Pull requests need to be based on and opened against the `master` branch.
|
||||||
* [Code review guidelines](https://github.com/ethersphere/swarm/blob/master/docs/Code-Review-Guidelines.md).
|
|
||||||
* Commit messages should be prefixed with the package(s) they modify.
|
* Commit messages should be prefixed with the package(s) they modify.
|
||||||
* E.g. "fuse: ignore default manifest entry"
|
* E.g. "eth, rpc: make trace configs optional"
|
||||||
|
|
||||||
|
Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide)
|
||||||
|
for more details on configuring your environment, managing project dependencies, and
|
||||||
|
testing procedures.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
The swarm library (i.e. all code outside of the `cmd` directory) is licensed under the
|
The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the
|
||||||
[GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html), also
|
[GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html),
|
||||||
included in our repository in the `COPYING.LESSER` file.
|
also included in our repository in the `COPYING.LESSER` file.
|
||||||
|
|
||||||
The swarm binaries (i.e. all code inside of the `cmd` directory) is licensed under the
|
The go-ethereum binaries (i.e. all code inside of the `cmd` directory) is licensed under the
|
||||||
[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also included
|
[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also
|
||||||
in our repository in the `COPYING` file.
|
included in our repository in the `COPYING` file.
|
||||||
|
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-----
|
||||||
|
```
|
186
accounts/abi/abi.go
Normal file
186
accounts/abi/abi.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
// Copyright 2015 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 abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The ABI holds information about a contract's context and available
|
||||||
|
// invokable methods. It will allow you to type check function calls and
|
||||||
|
// packs data accordingly.
|
||||||
|
type ABI struct {
|
||||||
|
Constructor Method
|
||||||
|
Methods map[string]Method
|
||||||
|
Events map[string]Event
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON returns a parsed ABI interface and error if it failed.
|
||||||
|
func JSON(reader io.Reader) (ABI, error) {
|
||||||
|
dec := json.NewDecoder(reader)
|
||||||
|
|
||||||
|
var abi ABI
|
||||||
|
if err := dec.Decode(&abi); err != nil {
|
||||||
|
return ABI{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return abi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack the given method name to conform the ABI. Method call's data
|
||||||
|
// will consist of method_id, args0, arg1, ... argN. Method id consists
|
||||||
|
// of 4 bytes and arguments are all 32 bytes.
|
||||||
|
// Method ids are created from the first 4 bytes of the hash of the
|
||||||
|
// methods string signature. (signature = baz(uint32,string32))
|
||||||
|
func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
|
||||||
|
// Fetch the ABI of the requested method
|
||||||
|
if name == "" {
|
||||||
|
// constructor
|
||||||
|
arguments, err := abi.Constructor.Inputs.Pack(args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return arguments, nil
|
||||||
|
}
|
||||||
|
method, exist := abi.Methods[name]
|
||||||
|
if !exist {
|
||||||
|
return nil, fmt.Errorf("method '%s' not found", name)
|
||||||
|
}
|
||||||
|
arguments, err := method.Inputs.Pack(args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Pack up the method ID too if not a constructor and return
|
||||||
|
return append(method.ID(), arguments...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unpack output in v according to the abi specification
|
||||||
|
func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) {
|
||||||
|
// since there can't be naming collisions with contracts and events,
|
||||||
|
// we need to decide whether we're calling a method or an event
|
||||||
|
if method, ok := abi.Methods[name]; ok {
|
||||||
|
if len(data)%32 != 0 {
|
||||||
|
return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data)
|
||||||
|
}
|
||||||
|
return method.Outputs.Unpack(v, data)
|
||||||
|
}
|
||||||
|
if event, ok := abi.Events[name]; ok {
|
||||||
|
return event.Inputs.Unpack(v, data)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("abi: could not locate named method or event")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnpackIntoMap unpacks a log into the provided map[string]interface{}
|
||||||
|
func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) {
|
||||||
|
// since there can't be naming collisions with contracts and events,
|
||||||
|
// we need to decide whether we're calling a method or an event
|
||||||
|
if method, ok := abi.Methods[name]; ok {
|
||||||
|
if len(data)%32 != 0 {
|
||||||
|
return fmt.Errorf("abi: improperly formatted output")
|
||||||
|
}
|
||||||
|
return method.Outputs.UnpackIntoMap(v, data)
|
||||||
|
}
|
||||||
|
if event, ok := abi.Events[name]; ok {
|
||||||
|
return event.Inputs.UnpackIntoMap(v, data)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("abi: could not locate named method or event")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface
|
||||||
|
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
|
var fields []struct {
|
||||||
|
Type string
|
||||||
|
Name string
|
||||||
|
Constant bool
|
||||||
|
Anonymous bool
|
||||||
|
Inputs []Argument
|
||||||
|
Outputs []Argument
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
abi.Methods = make(map[string]Method)
|
||||||
|
abi.Events = make(map[string]Event)
|
||||||
|
for _, field := range fields {
|
||||||
|
switch field.Type {
|
||||||
|
case "constructor":
|
||||||
|
abi.Constructor = Method{
|
||||||
|
Inputs: field.Inputs,
|
||||||
|
}
|
||||||
|
// empty defaults to function according to the abi spec
|
||||||
|
case "function", "":
|
||||||
|
name := field.Name
|
||||||
|
_, ok := abi.Methods[name]
|
||||||
|
for idx := 0; ok; idx++ {
|
||||||
|
name = fmt.Sprintf("%s%d", field.Name, idx)
|
||||||
|
_, ok = abi.Methods[name]
|
||||||
|
}
|
||||||
|
abi.Methods[name] = Method{
|
||||||
|
Name: name,
|
||||||
|
RawName: field.Name,
|
||||||
|
Const: field.Constant,
|
||||||
|
Inputs: field.Inputs,
|
||||||
|
Outputs: field.Outputs,
|
||||||
|
}
|
||||||
|
case "event":
|
||||||
|
name := field.Name
|
||||||
|
_, ok := abi.Events[name]
|
||||||
|
for idx := 0; ok; idx++ {
|
||||||
|
name = fmt.Sprintf("%s%d", field.Name, idx)
|
||||||
|
_, ok = abi.Events[name]
|
||||||
|
}
|
||||||
|
abi.Events[name] = Event{
|
||||||
|
Name: name,
|
||||||
|
RawName: field.Name,
|
||||||
|
Anonymous: field.Anonymous,
|
||||||
|
Inputs: field.Inputs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodById looks up a method by the 4-byte id
|
||||||
|
// returns nil if none found
|
||||||
|
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
|
||||||
|
if len(sigdata) < 4 {
|
||||||
|
return nil, fmt.Errorf("data too short (%d bytes) for abi method lookup", len(sigdata))
|
||||||
|
}
|
||||||
|
for _, method := range abi.Methods {
|
||||||
|
if bytes.Equal(method.ID(), sigdata[:4]) {
|
||||||
|
return &method, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no method with id: %#x", sigdata[:4])
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventByID looks an event up by its topic hash in the
|
||||||
|
// ABI and returns nil if none found.
|
||||||
|
func (abi *ABI) EventByID(topic common.Hash) (*Event, error) {
|
||||||
|
for _, event := range abi.Events {
|
||||||
|
if bytes.Equal(event.ID().Bytes(), topic.Bytes()) {
|
||||||
|
return &event, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no event with id: %#x", topic.Hex())
|
||||||
|
}
|
1053
accounts/abi/abi_test.go
Normal file
1053
accounts/abi/abi_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -34,10 +34,11 @@ type Argument struct {
|
|||||||
type Arguments []Argument
|
type Arguments []Argument
|
||||||
|
|
||||||
type ArgumentMarshaling struct {
|
type ArgumentMarshaling struct {
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
Components []ArgumentMarshaling
|
InternalType string
|
||||||
Indexed bool
|
Components []ArgumentMarshaling
|
||||||
|
Indexed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler interface
|
// UnmarshalJSON implements json.Unmarshaler interface
|
||||||
@ -48,7 +49,7 @@ func (argument *Argument) UnmarshalJSON(data []byte) error {
|
|||||||
return fmt.Errorf("argument json err: %v", err)
|
return fmt.Errorf("argument json err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
argument.Type, err = NewType(arg.Type, arg.Components)
|
argument.Type, err = NewType(arg.Type, arg.InternalType, arg.Components)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -88,6 +89,13 @@ func (arguments Arguments) isTuple() bool {
|
|||||||
|
|
||||||
// Unpack performs the operation hexdata -> Go format
|
// Unpack performs the operation hexdata -> Go format
|
||||||
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
|
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
if len(arguments) != 0 {
|
||||||
|
return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
|
||||||
|
} else {
|
||||||
|
return nil // Nothing to unmarshal, return
|
||||||
|
}
|
||||||
|
}
|
||||||
// make sure the passed value is arguments pointer
|
// make sure the passed value is arguments pointer
|
||||||
if reflect.Ptr != reflect.ValueOf(v).Kind() {
|
if reflect.Ptr != reflect.ValueOf(v).Kind() {
|
||||||
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
|
||||||
@ -104,11 +112,17 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error {
|
|||||||
|
|
||||||
// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value
|
// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value
|
||||||
func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error {
|
func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error {
|
||||||
|
if len(data) == 0 {
|
||||||
|
if len(arguments) != 0 {
|
||||||
|
return fmt.Errorf("abi: attempting to unmarshall an empty string while arguments are expected")
|
||||||
|
} else {
|
||||||
|
return nil // Nothing to unmarshal, return
|
||||||
|
}
|
||||||
|
}
|
||||||
marshalledValues, err := arguments.UnpackValues(data)
|
marshalledValues, err := arguments.UnpackValues(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return arguments.unpackIntoMap(v, marshalledValues)
|
return arguments.unpackIntoMap(v, marshalledValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,11 +133,22 @@ func unpack(t *Type, dst interface{}, src interface{}) error {
|
|||||||
dstVal = reflect.ValueOf(dst).Elem()
|
dstVal = reflect.ValueOf(dst).Elem()
|
||||||
srcVal = reflect.ValueOf(src)
|
srcVal = reflect.ValueOf(src)
|
||||||
)
|
)
|
||||||
|
tuple, typ := false, t
|
||||||
if t.T != TupleTy && !((t.T == SliceTy || t.T == ArrayTy) && t.Elem.T == TupleTy) {
|
for {
|
||||||
|
if typ.T == SliceTy || typ.T == ArrayTy {
|
||||||
|
typ = typ.Elem
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tuple = typ.T == TupleTy
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !tuple {
|
||||||
return set(dstVal, srcVal)
|
return set(dstVal, srcVal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dereferences interface or pointer wrapper
|
||||||
|
dstVal = indirectInterfaceOrPtr(dstVal)
|
||||||
|
|
||||||
switch t.T {
|
switch t.T {
|
||||||
case TupleTy:
|
case TupleTy:
|
||||||
if dstVal.Kind() != reflect.Struct {
|
if dstVal.Kind() != reflect.Struct {
|
||||||
@ -191,7 +216,7 @@ func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interfac
|
|||||||
argument := arguments.NonIndexed()[0]
|
argument := arguments.NonIndexed()[0]
|
||||||
elem := reflect.ValueOf(v).Elem()
|
elem := reflect.ValueOf(v).Elem()
|
||||||
|
|
||||||
if elem.Kind() == reflect.Struct {
|
if elem.Kind() == reflect.Struct && argument.Type.T != TupleTy {
|
||||||
fieldmap, err := mapArgNamesToStructFields([]string{argument.Name}, elem)
|
fieldmap, err := mapArgNamesToStructFields([]string{argument.Name}, elem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
96
accounts/abi/bind/auth.go
Normal file
96
accounts/abi/bind/auth.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// 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 bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/external"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewTransactor is a utility method to easily create a transaction signer from
|
||||||
|
// an encrypted json key stream and the associated passphrase.
|
||||||
|
func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
|
||||||
|
json, err := ioutil.ReadAll(keyin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key, err := keystore.DecryptKey(json, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewKeyedTransactor(key.PrivateKey), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeyStoreTransactor is a utility method to easily create a transaction signer from
|
||||||
|
// an decrypted key from a keystore
|
||||||
|
func NewKeyStoreTransactor(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 {
|
||||||
|
keyAddr := crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
return &TransactOpts{
|
||||||
|
From: keyAddr,
|
||||||
|
Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) {
|
||||||
|
if address != keyAddr {
|
||||||
|
return nil, errors.New("not authorized to sign this account")
|
||||||
|
}
|
||||||
|
signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tx.WithSignature(signer, signature)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClefTransactor is a utility method to easily create a transaction signer
|
||||||
|
// with a clef backend.
|
||||||
|
func NewClefTransactor(clef *external.ExternalSigner, account accounts.Account) *TransactOpts {
|
||||||
|
return &TransactOpts{
|
||||||
|
From: account.Address,
|
||||||
|
Signer: func(signer types.Signer, address common.Address, transaction *types.Transaction) (*types.Transaction, error) {
|
||||||
|
if address != account.Address {
|
||||||
|
return nil, errors.New("not authorized to sign this account")
|
||||||
|
}
|
||||||
|
return clef.SignTx(account, transaction, nil) // Clef enforces its own chain id
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
@ -45,8 +45,10 @@ import (
|
|||||||
// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
|
// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
|
||||||
var _ bind.ContractBackend = (*SimulatedBackend)(nil)
|
var _ bind.ContractBackend = (*SimulatedBackend)(nil)
|
||||||
|
|
||||||
var errBlockNumberUnsupported = errors.New("SimulatedBackend cannot access blocks other than the latest block")
|
var (
|
||||||
var errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction")
|
errBlockNumberUnsupported = errors.New("simulatedBackend cannot access blocks other than the latest block")
|
||||||
|
errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction")
|
||||||
|
)
|
||||||
|
|
||||||
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
|
// SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
|
||||||
// the background. Its main purpose is to allow easily testing contract bindings.
|
// the background. Its main purpose is to allow easily testing contract bindings.
|
||||||
@ -63,10 +65,9 @@ type SimulatedBackend struct {
|
|||||||
config *params.ChainConfig
|
config *params.ChainConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
// NewSimulatedBackendWithDatabase creates a new binding backend based on the given database
|
||||||
// for testing purposes.
|
// and uses a simulated blockchain for testing purposes.
|
||||||
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
func NewSimulatedBackendWithDatabase(database ethdb.Database, alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
||||||
database := rawdb.NewMemoryDatabase()
|
|
||||||
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc}
|
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc}
|
||||||
genesis.MustCommit(database)
|
genesis.MustCommit(database)
|
||||||
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil)
|
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{}, nil)
|
||||||
@ -81,6 +82,18 @@ func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBac
|
|||||||
return backend
|
return backend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
|
||||||
|
// for testing purposes.
|
||||||
|
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
|
||||||
|
return NewSimulatedBackendWithDatabase(rawdb.NewMemoryDatabase(), alloc, gasLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close terminates the underlying blockchain's update loop.
|
||||||
|
func (b *SimulatedBackend) Close() error {
|
||||||
|
b.blockchain.Stop()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Commit imports all the pending transactions as a single block and starts a
|
// Commit imports all the pending transactions as a single block and starts a
|
||||||
// fresh new state.
|
// fresh new state.
|
||||||
func (b *SimulatedBackend) Commit() {
|
func (b *SimulatedBackend) Commit() {
|
||||||
@ -316,7 +329,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
|
|||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
|
|
||||||
sender, err := types.Sender(types.HomesteadSigner{}, tx)
|
sender, err := types.Sender(types.NewEIP155Signer(b.config.ChainID), tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("invalid transaction: %v", err))
|
panic(fmt.Errorf("invalid transaction: %v", err))
|
||||||
}
|
}
|
||||||
@ -424,6 +437,11 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blockchain returns the underlying blockchain.
|
||||||
|
func (b *SimulatedBackend) Blockchain() *core.BlockChain {
|
||||||
|
return b.blockchain
|
||||||
|
}
|
||||||
|
|
||||||
// callmsg implements core.Message to allow passing it as a transaction simulator.
|
// callmsg implements core.Message to allow passing it as a transaction simulator.
|
||||||
type callmsg struct {
|
type callmsg struct {
|
||||||
ethereum.CallMsg
|
ethereum.CallMsg
|
83
accounts/abi/bind/backends/simulated_test.go
Normal file
83
accounts/abi/bind/backends/simulated_test.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// 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
|
||||||
|
// 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 backends_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
ethereum "github.com/ethereum/go-ethereum"
|
||||||
|
"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/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimulatedBackend(t *testing.T) {
|
||||||
|
var gasLimit uint64 = 8000029
|
||||||
|
key, _ := crypto.GenerateKey() // nolint: gosec
|
||||||
|
auth := bind.NewKeyedTransactor(key)
|
||||||
|
genAlloc := make(core.GenesisAlloc)
|
||||||
|
genAlloc[auth.From] = core.GenesisAccount{Balance: big.NewInt(9223372036854775807)}
|
||||||
|
|
||||||
|
sim := backends.NewSimulatedBackend(genAlloc, gasLimit)
|
||||||
|
defer sim.Close()
|
||||||
|
|
||||||
|
// should return an error if the tx is not found
|
||||||
|
txHash := common.HexToHash("2")
|
||||||
|
_, isPending, err := sim.TransactionByHash(context.Background(), txHash)
|
||||||
|
|
||||||
|
if isPending {
|
||||||
|
t.Fatal("transaction should not be pending")
|
||||||
|
}
|
||||||
|
if err != ethereum.NotFound {
|
||||||
|
t.Fatalf("err should be `ethereum.NotFound` but received %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a transaction and confirm you can retrieve it
|
||||||
|
code := `6060604052600a8060106000396000f360606040526008565b00`
|
||||||
|
var gas uint64 = 3000000
|
||||||
|
tx := types.NewContractCreation(0, big.NewInt(0), gas, big.NewInt(1), common.FromHex(code))
|
||||||
|
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, key)
|
||||||
|
|
||||||
|
err = sim.SendTransaction(context.Background(), tx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("error sending transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
txHash = tx.Hash()
|
||||||
|
_, isPending, err = sim.TransactionByHash(context.Background(), txHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting transaction with hash: %v", txHash.String())
|
||||||
|
}
|
||||||
|
if !isPending {
|
||||||
|
t.Fatal("transaction should have pending status")
|
||||||
|
}
|
||||||
|
|
||||||
|
sim.Commit()
|
||||||
|
_, isPending, err = sim.TransactionByHash(context.Background(), txHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error getting transaction with hash: %v", txHash.String())
|
||||||
|
}
|
||||||
|
if isPending {
|
||||||
|
t.Fatal("transaction should not have pending status")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -218,7 +218,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If the contract surely has code (or code is not needed), estimate the transaction
|
// If the contract surely has code (or code is not needed), estimate the transaction
|
||||||
msg := ethereum.CallMsg{From: opts.From, To: contract, Value: value, Data: input}
|
msg := ethereum.CallMsg{From: opts.From, To: contract, GasPrice: gasPrice, Value: value, Data: input}
|
||||||
gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg)
|
gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
|
return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
|
||||||
@ -252,7 +252,7 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int
|
|||||||
opts = new(FilterOpts)
|
opts = new(FilterOpts)
|
||||||
}
|
}
|
||||||
// Append the event selector to the query parameters and construct the topic set
|
// Append the event selector to the query parameters and construct the topic set
|
||||||
query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...)
|
query = append([][]interface{}{{c.abi.Events[name].ID()}}, query...)
|
||||||
|
|
||||||
topics, err := makeTopics(query...)
|
topics, err := makeTopics(query...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -301,7 +301,7 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter
|
|||||||
opts = new(WatchOpts)
|
opts = new(WatchOpts)
|
||||||
}
|
}
|
||||||
// Append the event selector to the query parameters and construct the topic set
|
// Append the event selector to the query parameters and construct the topic set
|
||||||
query = append([][]interface{}{{c.abi.Events[name].Id()}}, query...)
|
query = append([][]interface{}{{c.abi.Events[name].ID()}}, query...)
|
||||||
|
|
||||||
topics, err := makeTopics(query...)
|
topics, err := makeTopics(query...)
|
||||||
if err != nil {
|
if err != nil {
|
347
accounts/abi/bind/base_test.go
Normal file
347
accounts/abi/bind/base_test.go
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
// 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
|
||||||
|
// 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 bind_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"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/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockCaller struct {
|
||||||
|
codeAtBlockNumber *big.Int
|
||||||
|
callContractBlockNumber *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
|
||||||
|
mc.codeAtBlockNumber = blockNumber
|
||||||
|
return []byte{1, 2, 3}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||||
|
mc.callContractBlockNumber = blockNumber
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func TestPassingBlockNumber(t *testing.T) {
|
||||||
|
|
||||||
|
mc := &mockCaller{}
|
||||||
|
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
|
||||||
|
Methods: map[string]abi.Method{
|
||||||
|
"something": {
|
||||||
|
Name: "something",
|
||||||
|
Outputs: abi.Arguments{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, mc, nil, nil)
|
||||||
|
var ret string
|
||||||
|
|
||||||
|
blockNumber := big.NewInt(42)
|
||||||
|
|
||||||
|
bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, &ret, "something")
|
||||||
|
|
||||||
|
if mc.callContractBlockNumber != blockNumber {
|
||||||
|
t.Fatalf("CallContract() was not passed the block number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if mc.codeAtBlockNumber != blockNumber {
|
||||||
|
t.Fatalf("CodeAt() was not passed the block number")
|
||||||
|
}
|
||||||
|
|
||||||
|
bc.Call(&bind.CallOpts{}, &ret, "something")
|
||||||
|
|
||||||
|
if mc.callContractBlockNumber != nil {
|
||||||
|
t.Fatalf("CallContract() was passed a block number when it should not have been")
|
||||||
|
}
|
||||||
|
|
||||||
|
if mc.codeAtBlockNumber != nil {
|
||||||
|
t.Fatalf("CodeAt() was passed a block number when it should not have been")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158"
|
||||||
|
|
||||||
|
func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) {
|
||||||
|
hash := crypto.Keccak256Hash([]byte("testName"))
|
||||||
|
mockLog := types.Log{
|
||||||
|
Address: common.HexToAddress("0x0"),
|
||||||
|
Topics: []common.Hash{
|
||||||
|
common.HexToHash("0x0"),
|
||||||
|
hash,
|
||||||
|
},
|
||||||
|
Data: hexutil.MustDecode(hexData),
|
||||||
|
BlockNumber: uint64(26),
|
||||||
|
TxHash: common.HexToHash("0x0"),
|
||||||
|
TxIndex: 111,
|
||||||
|
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
|
||||||
|
Index: 7,
|
||||||
|
Removed: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||||
|
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
|
||||||
|
|
||||||
|
receivedMap := make(map[string]interface{})
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"name": hash,
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(receivedMap) != 4 {
|
||||||
|
t.Fatal("unpacked map expected to have length 4")
|
||||||
|
}
|
||||||
|
if receivedMap["name"] != expectedReceivedMap["name"] {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["sender"] != expectedReceivedMap["sender"] {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) {
|
||||||
|
sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hash := crypto.Keccak256Hash(sliceBytes)
|
||||||
|
mockLog := types.Log{
|
||||||
|
Address: common.HexToAddress("0x0"),
|
||||||
|
Topics: []common.Hash{
|
||||||
|
common.HexToHash("0x0"),
|
||||||
|
hash,
|
||||||
|
},
|
||||||
|
Data: hexutil.MustDecode(hexData),
|
||||||
|
BlockNumber: uint64(26),
|
||||||
|
TxHash: common.HexToHash("0x0"),
|
||||||
|
TxIndex: 111,
|
||||||
|
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
|
||||||
|
Index: 7,
|
||||||
|
Removed: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||||
|
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
|
||||||
|
|
||||||
|
receivedMap := make(map[string]interface{})
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"names": hash,
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(receivedMap) != 4 {
|
||||||
|
t.Fatal("unpacked map expected to have length 4")
|
||||||
|
}
|
||||||
|
if receivedMap["names"] != expectedReceivedMap["names"] {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["sender"] != expectedReceivedMap["sender"] {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) {
|
||||||
|
arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hash := crypto.Keccak256Hash(arrBytes)
|
||||||
|
mockLog := types.Log{
|
||||||
|
Address: common.HexToAddress("0x0"),
|
||||||
|
Topics: []common.Hash{
|
||||||
|
common.HexToHash("0x0"),
|
||||||
|
hash,
|
||||||
|
},
|
||||||
|
Data: hexutil.MustDecode(hexData),
|
||||||
|
BlockNumber: uint64(26),
|
||||||
|
TxHash: common.HexToHash("0x0"),
|
||||||
|
TxIndex: 111,
|
||||||
|
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
|
||||||
|
Index: 7,
|
||||||
|
Removed: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||||
|
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
|
||||||
|
|
||||||
|
receivedMap := make(map[string]interface{})
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"addresses": hash,
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(receivedMap) != 4 {
|
||||||
|
t.Fatal("unpacked map expected to have length 4")
|
||||||
|
}
|
||||||
|
if receivedMap["addresses"] != expectedReceivedMap["addresses"] {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["sender"] != expectedReceivedMap["sender"] {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) {
|
||||||
|
mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")
|
||||||
|
addrBytes := mockAddress.Bytes()
|
||||||
|
hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)"))
|
||||||
|
functionSelector := hash[:4]
|
||||||
|
functionTyBytes := append(addrBytes, functionSelector...)
|
||||||
|
var functionTy [24]byte
|
||||||
|
copy(functionTy[:], functionTyBytes[0:24])
|
||||||
|
mockLog := types.Log{
|
||||||
|
Address: common.HexToAddress("0x0"),
|
||||||
|
Topics: []common.Hash{
|
||||||
|
common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"),
|
||||||
|
common.BytesToHash(functionTyBytes),
|
||||||
|
},
|
||||||
|
Data: hexutil.MustDecode(hexData),
|
||||||
|
BlockNumber: uint64(26),
|
||||||
|
TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"),
|
||||||
|
TxIndex: 111,
|
||||||
|
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
|
||||||
|
Index: 7,
|
||||||
|
Removed: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||||
|
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
|
||||||
|
|
||||||
|
receivedMap := make(map[string]interface{})
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"function": functionTy,
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(receivedMap) != 4 {
|
||||||
|
t.Fatal("unpacked map expected to have length 4")
|
||||||
|
}
|
||||||
|
if receivedMap["function"] != expectedReceivedMap["function"] {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["sender"] != expectedReceivedMap["sender"] {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) {
|
||||||
|
byts := []byte{1, 2, 3, 4, 5}
|
||||||
|
hash := crypto.Keccak256Hash(byts)
|
||||||
|
mockLog := types.Log{
|
||||||
|
Address: common.HexToAddress("0x0"),
|
||||||
|
Topics: []common.Hash{
|
||||||
|
common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"),
|
||||||
|
hash,
|
||||||
|
},
|
||||||
|
Data: hexutil.MustDecode(hexData),
|
||||||
|
BlockNumber: uint64(26),
|
||||||
|
TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"),
|
||||||
|
TxIndex: 111,
|
||||||
|
BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}),
|
||||||
|
Index: 7,
|
||||||
|
Removed: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]`
|
||||||
|
parsedAbi, _ := abi.JSON(strings.NewReader(abiString))
|
||||||
|
bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil)
|
||||||
|
|
||||||
|
receivedMap := make(map[string]interface{})
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"content": hash,
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []byte{88},
|
||||||
|
}
|
||||||
|
if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(receivedMap) != 4 {
|
||||||
|
t.Fatal("unpacked map expected to have length 4")
|
||||||
|
}
|
||||||
|
if receivedMap["content"] != expectedReceivedMap["content"] {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["sender"] != expectedReceivedMap["sender"] {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) {
|
||||||
|
t.Error("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
}
|
640
accounts/abi/bind/bind.go
Normal file
640
accounts/abi/bind/bind.go
Normal file
@ -0,0 +1,640 @@
|
|||||||
|
// 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 bind generates Ethereum contract Go bindings.
|
||||||
|
//
|
||||||
|
// Detailed usage document and tutorial available on the go-ethereum Wiki page:
|
||||||
|
// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts
|
||||||
|
package bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Lang is a target programming language selector to generate bindings for.
|
||||||
|
type Lang int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LangGo Lang = iota
|
||||||
|
LangJava
|
||||||
|
LangObjC
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant
|
||||||
|
// to be used as is in client code, but rather as an intermediate struct which
|
||||||
|
// enforces compile time type safety and naming convention opposed to having to
|
||||||
|
// manually maintain hard coded strings that break on runtime.
|
||||||
|
func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) {
|
||||||
|
var (
|
||||||
|
// contracts is the map of each individual contract requested binding
|
||||||
|
contracts = make(map[string]*tmplContract)
|
||||||
|
|
||||||
|
// structs is the map of all reclared structs shared by passed contracts.
|
||||||
|
structs = make(map[string]*tmplStruct)
|
||||||
|
|
||||||
|
// isLib is the map used to flag each encountered library as such
|
||||||
|
isLib = make(map[string]struct{})
|
||||||
|
)
|
||||||
|
for i := 0; i < len(types); i++ {
|
||||||
|
// Parse the actual ABI to generate the binding for
|
||||||
|
evmABI, err := abi.JSON(strings.NewReader(abis[i]))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Strip any whitespace from the JSON ABI
|
||||||
|
strippedABI := strings.Map(func(r rune) rune {
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}, abis[i])
|
||||||
|
|
||||||
|
// Extract the call and transact methods; events, struct definitions; and sort them alphabetically
|
||||||
|
var (
|
||||||
|
calls = make(map[string]*tmplMethod)
|
||||||
|
transacts = make(map[string]*tmplMethod)
|
||||||
|
events = make(map[string]*tmplEvent)
|
||||||
|
|
||||||
|
// identifiers are used to detect duplicated identifier of function
|
||||||
|
// and event. For all calls, transacts and events, abigen will generate
|
||||||
|
// corresponding bindings. However we have to ensure there is no
|
||||||
|
// identifier coliision in the bindings of these categories.
|
||||||
|
callIdentifiers = make(map[string]bool)
|
||||||
|
transactIdentifiers = make(map[string]bool)
|
||||||
|
eventIdentifiers = make(map[string]bool)
|
||||||
|
)
|
||||||
|
for _, original := range evmABI.Methods {
|
||||||
|
// Normalize the method for capital cases and non-anonymous inputs/outputs
|
||||||
|
normalized := original
|
||||||
|
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
|
||||||
|
// Ensure there is no duplicated identifier
|
||||||
|
var identifiers = callIdentifiers
|
||||||
|
if !original.Const {
|
||||||
|
identifiers = transactIdentifiers
|
||||||
|
}
|
||||||
|
if identifiers[normalizedName] {
|
||||||
|
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
||||||
|
}
|
||||||
|
identifiers[normalizedName] = true
|
||||||
|
normalized.Name = normalizedName
|
||||||
|
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
|
||||||
|
copy(normalized.Inputs, original.Inputs)
|
||||||
|
for j, input := range normalized.Inputs {
|
||||||
|
if input.Name == "" {
|
||||||
|
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
||||||
|
}
|
||||||
|
if hasStruct(input.Type) {
|
||||||
|
bindStructType[lang](input.Type, structs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
normalized.Outputs = make([]abi.Argument, len(original.Outputs))
|
||||||
|
copy(normalized.Outputs, original.Outputs)
|
||||||
|
for j, output := range normalized.Outputs {
|
||||||
|
if output.Name != "" {
|
||||||
|
normalized.Outputs[j].Name = capitalise(output.Name)
|
||||||
|
}
|
||||||
|
if hasStruct(output.Type) {
|
||||||
|
bindStructType[lang](output.Type, structs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Append the methods to the call or transact lists
|
||||||
|
if original.Const {
|
||||||
|
calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
|
||||||
|
} else {
|
||||||
|
transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, original := range evmABI.Events {
|
||||||
|
// Skip anonymous events as they don't support explicit filtering
|
||||||
|
if original.Anonymous {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Normalize the event for capital cases and non-anonymous outputs
|
||||||
|
normalized := original
|
||||||
|
|
||||||
|
// Ensure there is no duplicated identifier
|
||||||
|
normalizedName := methodNormalizer[lang](alias(aliases, original.Name))
|
||||||
|
if eventIdentifiers[normalizedName] {
|
||||||
|
return "", fmt.Errorf("duplicated identifier \"%s\"(normalized \"%s\"), use --alias for renaming", original.Name, normalizedName)
|
||||||
|
}
|
||||||
|
eventIdentifiers[normalizedName] = true
|
||||||
|
normalized.Name = normalizedName
|
||||||
|
|
||||||
|
normalized.Inputs = make([]abi.Argument, len(original.Inputs))
|
||||||
|
copy(normalized.Inputs, original.Inputs)
|
||||||
|
for j, input := range normalized.Inputs {
|
||||||
|
if input.Name == "" {
|
||||||
|
normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j)
|
||||||
|
}
|
||||||
|
if hasStruct(input.Type) {
|
||||||
|
bindStructType[lang](input.Type, structs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Append the event to the accumulator list
|
||||||
|
events[original.Name] = &tmplEvent{Original: original, Normalized: normalized}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is no easy way to pass arbitrary java objects to the Go side.
|
||||||
|
if len(structs) > 0 && lang == LangJava {
|
||||||
|
return "", errors.New("java binding for tuple arguments is not supported yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
contracts[types[i]] = &tmplContract{
|
||||||
|
Type: capitalise(types[i]),
|
||||||
|
InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1),
|
||||||
|
InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"),
|
||||||
|
Constructor: evmABI.Constructor,
|
||||||
|
Calls: calls,
|
||||||
|
Transacts: transacts,
|
||||||
|
Events: events,
|
||||||
|
Libraries: make(map[string]string),
|
||||||
|
}
|
||||||
|
// Function 4-byte signatures are stored in the same sequence
|
||||||
|
// as types, if available.
|
||||||
|
if len(fsigs) > i {
|
||||||
|
contracts[types[i]].FuncSigs = fsigs[i]
|
||||||
|
}
|
||||||
|
// Parse library references.
|
||||||
|
for pattern, name := range libs {
|
||||||
|
matched, err := regexp.Match("__\\$"+pattern+"\\$__", []byte(contracts[types[i]].InputBin))
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Could not search for pattern", "pattern", pattern, "contract", contracts[types[i]], "err", err)
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
contracts[types[i]].Libraries[pattern] = name
|
||||||
|
// keep track that this type is a library
|
||||||
|
if _, ok := isLib[name]; !ok {
|
||||||
|
isLib[name] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if that type has already been identified as a library
|
||||||
|
for i := 0; i < len(types); i++ {
|
||||||
|
_, ok := isLib[types[i]]
|
||||||
|
contracts[types[i]].Library = ok
|
||||||
|
}
|
||||||
|
// Generate the contract template data content and render it
|
||||||
|
data := &tmplData{
|
||||||
|
Package: pkg,
|
||||||
|
Contracts: contracts,
|
||||||
|
Libraries: libs,
|
||||||
|
Structs: structs,
|
||||||
|
}
|
||||||
|
buffer := new(bytes.Buffer)
|
||||||
|
|
||||||
|
funcs := map[string]interface{}{
|
||||||
|
"bindtype": bindType[lang],
|
||||||
|
"bindtopictype": bindTopicType[lang],
|
||||||
|
"namedtype": namedType[lang],
|
||||||
|
"formatmethod": formatMethod,
|
||||||
|
"formatevent": formatEvent,
|
||||||
|
"capitalise": capitalise,
|
||||||
|
"decapitalise": decapitalise,
|
||||||
|
}
|
||||||
|
tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang]))
|
||||||
|
if err := tmpl.Execute(buffer, data); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// For Go bindings pass the code through gofmt to clean it up
|
||||||
|
if lang == LangGo {
|
||||||
|
code, err := format.Source(buffer.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("%v\n%s", err, buffer)
|
||||||
|
}
|
||||||
|
return string(code), nil
|
||||||
|
}
|
||||||
|
// For all others just return as is for now
|
||||||
|
return buffer.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindType is a set of type binders that convert Solidity types to some supported
|
||||||
|
// programming language types.
|
||||||
|
var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||||
|
LangGo: bindTypeGo,
|
||||||
|
LangJava: bindTypeJava,
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go one.
|
||||||
|
func bindBasicTypeGo(kind abi.Type) string {
|
||||||
|
switch kind.T {
|
||||||
|
case abi.AddressTy:
|
||||||
|
return "common.Address"
|
||||||
|
case abi.IntTy, abi.UintTy:
|
||||||
|
parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String())
|
||||||
|
switch parts[2] {
|
||||||
|
case "8", "16", "32", "64":
|
||||||
|
return fmt.Sprintf("%sint%s", parts[1], parts[2])
|
||||||
|
}
|
||||||
|
return "*big.Int"
|
||||||
|
case abi.FixedBytesTy:
|
||||||
|
return fmt.Sprintf("[%d]byte", kind.Size)
|
||||||
|
case abi.BytesTy:
|
||||||
|
return "[]byte"
|
||||||
|
case abi.FunctionTy:
|
||||||
|
return "[24]byte"
|
||||||
|
default:
|
||||||
|
// string, bool types
|
||||||
|
return kind.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindTypeGo converts solidity types to Go ones. Since there is no clear mapping
|
||||||
|
// from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly
|
||||||
|
// mapped will use an upscaled type (e.g. BigDecimal).
|
||||||
|
func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
|
switch kind.T {
|
||||||
|
case abi.TupleTy:
|
||||||
|
return structs[kind.TupleRawName+kind.String()].Name
|
||||||
|
case abi.ArrayTy:
|
||||||
|
return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs)
|
||||||
|
case abi.SliceTy:
|
||||||
|
return "[]" + bindTypeGo(*kind.Elem, structs)
|
||||||
|
default:
|
||||||
|
return bindBasicTypeGo(kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindBasicTypeJava converts basic solidity types(except array, slice and tuple) to Java one.
|
||||||
|
func bindBasicTypeJava(kind abi.Type) string {
|
||||||
|
switch kind.T {
|
||||||
|
case abi.AddressTy:
|
||||||
|
return "Address"
|
||||||
|
case abi.IntTy, abi.UintTy:
|
||||||
|
// Note that uint and int (without digits) are also matched,
|
||||||
|
// these are size 256, and will translate to BigInt (the default).
|
||||||
|
parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String())
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return kind.String()
|
||||||
|
}
|
||||||
|
// All unsigned integers should be translated to BigInt since gomobile doesn't
|
||||||
|
// support them.
|
||||||
|
if parts[1] == "u" {
|
||||||
|
return "BigInt"
|
||||||
|
}
|
||||||
|
|
||||||
|
namedSize := map[string]string{
|
||||||
|
"8": "byte",
|
||||||
|
"16": "short",
|
||||||
|
"32": "int",
|
||||||
|
"64": "long",
|
||||||
|
}[parts[2]]
|
||||||
|
|
||||||
|
// default to BigInt
|
||||||
|
if namedSize == "" {
|
||||||
|
namedSize = "BigInt"
|
||||||
|
}
|
||||||
|
return namedSize
|
||||||
|
case abi.FixedBytesTy, abi.BytesTy:
|
||||||
|
return "byte[]"
|
||||||
|
case abi.BoolTy:
|
||||||
|
return "boolean"
|
||||||
|
case abi.StringTy:
|
||||||
|
return "String"
|
||||||
|
case abi.FunctionTy:
|
||||||
|
return "byte[24]"
|
||||||
|
default:
|
||||||
|
return kind.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pluralizeJavaType explicitly converts multidimensional types to predefined
|
||||||
|
// type in go side.
|
||||||
|
func pluralizeJavaType(typ string) string {
|
||||||
|
switch typ {
|
||||||
|
case "boolean":
|
||||||
|
return "Bools"
|
||||||
|
case "String":
|
||||||
|
return "Strings"
|
||||||
|
case "Address":
|
||||||
|
return "Addresses"
|
||||||
|
case "byte[]":
|
||||||
|
return "Binaries"
|
||||||
|
case "BigInt":
|
||||||
|
return "BigInts"
|
||||||
|
}
|
||||||
|
return typ + "[]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping
|
||||||
|
// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly
|
||||||
|
// mapped will use an upscaled type (e.g. BigDecimal).
|
||||||
|
func bindTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
|
switch kind.T {
|
||||||
|
case abi.TupleTy:
|
||||||
|
return structs[kind.TupleRawName+kind.String()].Name
|
||||||
|
case abi.ArrayTy, abi.SliceTy:
|
||||||
|
return pluralizeJavaType(bindTypeJava(*kind.Elem, structs))
|
||||||
|
default:
|
||||||
|
return bindBasicTypeJava(kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindTopicType is a set of type binders that convert Solidity types to some
|
||||||
|
// supported programming language topic types.
|
||||||
|
var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||||
|
LangGo: bindTopicTypeGo,
|
||||||
|
LangJava: bindTopicTypeJava,
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same
|
||||||
|
// funcionality as for simple types, but dynamic types get converted to hashes.
|
||||||
|
func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
|
bound := bindTypeGo(kind, structs)
|
||||||
|
|
||||||
|
// todo(rjl493456442) according solidity documentation, indexed event
|
||||||
|
// parameters that are not value types i.e. arrays and structs are not
|
||||||
|
// stored directly but instead a keccak256-hash of an encoding is stored.
|
||||||
|
//
|
||||||
|
// We only convert stringS and bytes to hash, still need to deal with
|
||||||
|
// array(both fixed-size and dynamic-size) and struct.
|
||||||
|
if bound == "string" || bound == "[]byte" {
|
||||||
|
bound = "common.Hash"
|
||||||
|
}
|
||||||
|
return bound
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindTopicTypeJava converts a Solidity topic type to a Java one. It is almost the same
|
||||||
|
// funcionality as for simple types, but dynamic types get converted to hashes.
|
||||||
|
func bindTopicTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
|
bound := bindTypeJava(kind, structs)
|
||||||
|
|
||||||
|
// todo(rjl493456442) according solidity documentation, indexed event
|
||||||
|
// parameters that are not value types i.e. arrays and structs are not
|
||||||
|
// stored directly but instead a keccak256-hash of an encoding is stored.
|
||||||
|
//
|
||||||
|
// We only convert stringS and bytes to hash, still need to deal with
|
||||||
|
// array(both fixed-size and dynamic-size) and struct.
|
||||||
|
if bound == "String" || bound == "byte[]" {
|
||||||
|
bound = "Hash"
|
||||||
|
}
|
||||||
|
return bound
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindStructType is a set of type binders that convert Solidity tuple types to some supported
|
||||||
|
// programming language struct definition.
|
||||||
|
var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{
|
||||||
|
LangGo: bindStructTypeGo,
|
||||||
|
LangJava: bindStructTypeJava,
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping
|
||||||
|
// in the given map.
|
||||||
|
// Notably, this function will resolve and record nested struct recursively.
|
||||||
|
func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
|
switch kind.T {
|
||||||
|
case abi.TupleTy:
|
||||||
|
// We compose raw struct name and canonical parameter expression
|
||||||
|
// together here. The reason is before solidity v0.5.11, kind.TupleRawName
|
||||||
|
// is empty, so we use canonical parameter expression to distinguish
|
||||||
|
// different struct definition. From the consideration of backward
|
||||||
|
// compatibility, we concat these two together so that if kind.TupleRawName
|
||||||
|
// is not empty, it can have unique id.
|
||||||
|
id := kind.TupleRawName + kind.String()
|
||||||
|
if s, exist := structs[id]; exist {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
var fields []*tmplField
|
||||||
|
for i, elem := range kind.TupleElems {
|
||||||
|
field := bindStructTypeGo(*elem, structs)
|
||||||
|
fields = append(fields, &tmplField{Type: field, Name: capitalise(kind.TupleRawNames[i]), SolKind: *elem})
|
||||||
|
}
|
||||||
|
name := kind.TupleRawName
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("Struct%d", len(structs))
|
||||||
|
}
|
||||||
|
structs[id] = &tmplStruct{
|
||||||
|
Name: name,
|
||||||
|
Fields: fields,
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
case abi.ArrayTy:
|
||||||
|
return fmt.Sprintf("[%d]", kind.Size) + bindStructTypeGo(*kind.Elem, structs)
|
||||||
|
case abi.SliceTy:
|
||||||
|
return "[]" + bindStructTypeGo(*kind.Elem, structs)
|
||||||
|
default:
|
||||||
|
return bindBasicTypeGo(kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bindStructTypeJava converts a Solidity tuple type to a Java one and records the mapping
|
||||||
|
// in the given map.
|
||||||
|
// Notably, this function will resolve and record nested struct recursively.
|
||||||
|
func bindStructTypeJava(kind abi.Type, structs map[string]*tmplStruct) string {
|
||||||
|
switch kind.T {
|
||||||
|
case abi.TupleTy:
|
||||||
|
// We compose raw struct name and canonical parameter expression
|
||||||
|
// together here. The reason is before solidity v0.5.11, kind.TupleRawName
|
||||||
|
// is empty, so we use canonical parameter expression to distinguish
|
||||||
|
// different struct definition. From the consideration of backward
|
||||||
|
// compatibility, we concat these two together so that if kind.TupleRawName
|
||||||
|
// is not empty, it can have unique id.
|
||||||
|
id := kind.TupleRawName + kind.String()
|
||||||
|
if s, exist := structs[id]; exist {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
var fields []*tmplField
|
||||||
|
for i, elem := range kind.TupleElems {
|
||||||
|
field := bindStructTypeJava(*elem, structs)
|
||||||
|
fields = append(fields, &tmplField{Type: field, Name: decapitalise(kind.TupleRawNames[i]), SolKind: *elem})
|
||||||
|
}
|
||||||
|
name := kind.TupleRawName
|
||||||
|
if name == "" {
|
||||||
|
name = fmt.Sprintf("Class%d", len(structs))
|
||||||
|
}
|
||||||
|
structs[id] = &tmplStruct{
|
||||||
|
Name: name,
|
||||||
|
Fields: fields,
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
case abi.ArrayTy, abi.SliceTy:
|
||||||
|
return pluralizeJavaType(bindStructTypeJava(*kind.Elem, structs))
|
||||||
|
default:
|
||||||
|
return bindBasicTypeJava(kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// namedType is a set of functions that transform language specific types to
|
||||||
|
// named versions that my be used inside method names.
|
||||||
|
var namedType = map[Lang]func(string, abi.Type) string{
|
||||||
|
LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") },
|
||||||
|
LangJava: namedTypeJava,
|
||||||
|
}
|
||||||
|
|
||||||
|
// namedTypeJava converts some primitive data types to named variants that can
|
||||||
|
// be used as parts of method names.
|
||||||
|
func namedTypeJava(javaKind string, solKind abi.Type) string {
|
||||||
|
switch javaKind {
|
||||||
|
case "byte[]":
|
||||||
|
return "Binary"
|
||||||
|
case "boolean":
|
||||||
|
return "Bool"
|
||||||
|
default:
|
||||||
|
parts := regexp.MustCompile(`(u)?int([0-9]*)(\[[0-9]*\])?`).FindStringSubmatch(solKind.String())
|
||||||
|
if len(parts) != 4 {
|
||||||
|
return javaKind
|
||||||
|
}
|
||||||
|
switch parts[2] {
|
||||||
|
case "8", "16", "32", "64":
|
||||||
|
if parts[3] == "" {
|
||||||
|
return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2]))
|
||||||
|
}
|
||||||
|
return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2]))
|
||||||
|
|
||||||
|
default:
|
||||||
|
return javaKind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// alias returns an alias of the given string based on the aliasing rules
|
||||||
|
// or returns itself if no rule is matched.
|
||||||
|
func alias(aliases map[string]string, n string) string {
|
||||||
|
if alias, exist := aliases[n]; exist {
|
||||||
|
return alias
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodNormalizer is a name transformer that modifies Solidity method names to
|
||||||
|
// conform to target language naming concentions.
|
||||||
|
var methodNormalizer = map[Lang]func(string) string{
|
||||||
|
LangGo: abi.ToCamelCase,
|
||||||
|
LangJava: decapitalise,
|
||||||
|
}
|
||||||
|
|
||||||
|
// capitalise makes a camel-case string which starts with an upper case character.
|
||||||
|
func capitalise(input string) string {
|
||||||
|
return abi.ToCamelCase(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decapitalise makes a camel-case string which starts with a lower case character.
|
||||||
|
func decapitalise(input string) string {
|
||||||
|
if len(input) == 0 {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
|
||||||
|
goForm := abi.ToCamelCase(input)
|
||||||
|
return strings.ToLower(goForm[:1]) + goForm[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// structured checks whether a list of ABI data types has enough information to
|
||||||
|
// operate through a proper Go struct or if flat returns are needed.
|
||||||
|
func structured(args abi.Arguments) bool {
|
||||||
|
if len(args) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
exists := make(map[string]bool)
|
||||||
|
for _, out := range args {
|
||||||
|
// If the name is anonymous, we can't organize into a struct
|
||||||
|
if out.Name == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// If the field name is empty when normalized or collides (var, Var, _var, _Var),
|
||||||
|
// we can't organize into a struct
|
||||||
|
field := capitalise(out.Name)
|
||||||
|
if field == "" || exists[field] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
exists[field] = true
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasStruct returns an indicator whether the given type is struct, struct slice
|
||||||
|
// or struct array.
|
||||||
|
func hasStruct(t abi.Type) bool {
|
||||||
|
switch t.T {
|
||||||
|
case abi.SliceTy:
|
||||||
|
return hasStruct(*t.Elem)
|
||||||
|
case abi.ArrayTy:
|
||||||
|
return hasStruct(*t.Elem)
|
||||||
|
case abi.TupleTy:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveArgName converts a raw argument representation into a user friendly format.
|
||||||
|
func resolveArgName(arg abi.Argument, structs map[string]*tmplStruct) string {
|
||||||
|
var (
|
||||||
|
prefix string
|
||||||
|
embedded string
|
||||||
|
typ = &arg.Type
|
||||||
|
)
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
switch typ.T {
|
||||||
|
case abi.SliceTy:
|
||||||
|
prefix += "[]"
|
||||||
|
case abi.ArrayTy:
|
||||||
|
prefix += fmt.Sprintf("[%d]", typ.Size)
|
||||||
|
default:
|
||||||
|
embedded = typ.TupleRawName + typ.String()
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
typ = typ.Elem
|
||||||
|
}
|
||||||
|
if s, exist := structs[embedded]; exist {
|
||||||
|
return prefix + s.Name
|
||||||
|
} else {
|
||||||
|
return arg.Type.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatMethod transforms raw method representation into a user friendly one.
|
||||||
|
func formatMethod(method abi.Method, structs map[string]*tmplStruct) string {
|
||||||
|
inputs := make([]string, len(method.Inputs))
|
||||||
|
for i, input := range method.Inputs {
|
||||||
|
inputs[i] = fmt.Sprintf("%v %v", resolveArgName(input, structs), input.Name)
|
||||||
|
}
|
||||||
|
outputs := make([]string, len(method.Outputs))
|
||||||
|
for i, output := range method.Outputs {
|
||||||
|
outputs[i] = resolveArgName(output, structs)
|
||||||
|
if len(output.Name) > 0 {
|
||||||
|
outputs[i] += fmt.Sprintf(" %v", output.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
constant := ""
|
||||||
|
if method.Const {
|
||||||
|
constant = "constant "
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.RawName, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatEvent transforms raw event representation into a user friendly one.
|
||||||
|
func formatEvent(event abi.Event, structs map[string]*tmplStruct) string {
|
||||||
|
inputs := make([]string, len(event.Inputs))
|
||||||
|
for i, input := range event.Inputs {
|
||||||
|
if input.Indexed {
|
||||||
|
inputs[i] = fmt.Sprintf("%v indexed %v", resolveArgName(input, structs), input.Name)
|
||||||
|
} else {
|
||||||
|
inputs[i] = fmt.Sprintf("%v %v", resolveArgName(input, structs), input.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("event %v(%v)", event.RawName, strings.Join(inputs, ", "))
|
||||||
|
}
|
2006
accounts/abi/bind/bind_test.go
Normal file
2006
accounts/abi/bind/bind_test.go
Normal file
File diff suppressed because one or more lines are too long
616
accounts/abi/bind/template.go
Normal file
616
accounts/abi/bind/template.go
Normal file
@ -0,0 +1,616 @@
|
|||||||
|
// 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 bind
|
||||||
|
|
||||||
|
import "github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
|
|
||||||
|
// tmplData is the data structure required to fill the binding template.
|
||||||
|
type tmplData struct {
|
||||||
|
Package string // Name of the package to place the generated file in
|
||||||
|
Contracts map[string]*tmplContract // List of contracts to generate into this file
|
||||||
|
Libraries map[string]string // Map the bytecode's link pattern to the library name
|
||||||
|
Structs map[string]*tmplStruct // Contract struct type definitions
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmplContract contains the data needed to generate an individual contract binding.
|
||||||
|
type tmplContract struct {
|
||||||
|
Type string // Type name of the main contract binding
|
||||||
|
InputABI string // JSON ABI used as the input to generate the binding from
|
||||||
|
InputBin string // Optional EVM bytecode used to denetare deploy code from
|
||||||
|
FuncSigs map[string]string // Optional map: string signature -> 4-byte signature
|
||||||
|
Constructor abi.Method // Contract constructor for deploy parametrization
|
||||||
|
Calls map[string]*tmplMethod // Contract calls that only read state data
|
||||||
|
Transacts map[string]*tmplMethod // Contract calls that write state data
|
||||||
|
Events map[string]*tmplEvent // Contract events accessors
|
||||||
|
Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs
|
||||||
|
Library bool // Indicator whether the contract is a library
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed
|
||||||
|
// and cached data fields.
|
||||||
|
type tmplMethod struct {
|
||||||
|
Original abi.Method // Original method as parsed by the abi package
|
||||||
|
Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns)
|
||||||
|
Structured bool // Whether the returns should be accumulated into a struct
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmplEvent is a wrapper around an a
|
||||||
|
type tmplEvent struct {
|
||||||
|
Original abi.Event // Original event as parsed by the abi package
|
||||||
|
Normalized abi.Event // Normalized version of the parsed fields
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmplField is a wrapper around a struct field with binding language
|
||||||
|
// struct type definition and relative filed name.
|
||||||
|
type tmplField struct {
|
||||||
|
Type string // Field type representation depends on target binding language
|
||||||
|
Name string // Field name converted from the raw user-defined field name
|
||||||
|
SolKind abi.Type // Raw abi type information
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmplStruct is a wrapper around an abi.tuple contains a auto-generated
|
||||||
|
// struct name.
|
||||||
|
type tmplStruct struct {
|
||||||
|
Name string // Auto-generated struct name(before solidity v0.5.11) or raw name.
|
||||||
|
Fields []*tmplField // Struct fields definition depends on the binding language.
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmplSource is language to template mapping containing all the supported
|
||||||
|
// programming languages the package can generate to.
|
||||||
|
var tmplSource = map[Lang]string{
|
||||||
|
LangGo: tmplSourceGo,
|
||||||
|
LangJava: tmplSourceJava,
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmplSourceGo is the Go source template use to generate the contract binding
|
||||||
|
// based on.
|
||||||
|
const tmplSourceGo = `
|
||||||
|
// Code generated - DO NOT EDIT.
|
||||||
|
// This file is a generated binding and any manual changes will be lost.
|
||||||
|
|
||||||
|
package {{.Package}}
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var (
|
||||||
|
_ = big.NewInt
|
||||||
|
_ = strings.NewReader
|
||||||
|
_ = ethereum.NotFound
|
||||||
|
_ = abi.U256
|
||||||
|
_ = bind.Bind
|
||||||
|
_ = common.Big1
|
||||||
|
_ = types.BloomLookup
|
||||||
|
_ = event.NewSubscription
|
||||||
|
)
|
||||||
|
|
||||||
|
{{$structs := .Structs}}
|
||||||
|
{{range $structs}}
|
||||||
|
// {{.Name}} is an auto generated low-level Go binding around an user-defined struct.
|
||||||
|
type {{.Name}} struct {
|
||||||
|
{{range $field := .Fields}}
|
||||||
|
{{$field.Name}} {{$field.Type}}{{end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range $contract := .Contracts}}
|
||||||
|
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
||||||
|
const {{.Type}}ABI = "{{.InputABI}}"
|
||||||
|
|
||||||
|
{{if $contract.FuncSigs}}
|
||||||
|
// {{.Type}}FuncSigs maps the 4-byte function signature to its string representation.
|
||||||
|
var {{.Type}}FuncSigs = map[string]string{
|
||||||
|
{{range $strsig, $binsig := .FuncSigs}}"{{$binsig}}": "{{$strsig}}",
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .InputBin}}
|
||||||
|
// {{.Type}}Bin is the compiled bytecode used for deploying new contracts.
|
||||||
|
var {{.Type}}Bin = "0x{{.InputBin}}"
|
||||||
|
|
||||||
|
// Deploy{{.Type}} deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
||||||
|
func Deploy{{.Type}}(auth *bind.TransactOpts, backend bind.ContractBackend {{range .Constructor.Inputs}}, {{.Name}} {{bindtype .Type $structs}}{{end}}) (common.Address, *types.Transaction, *{{.Type}}, error) {
|
||||||
|
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, nil, err
|
||||||
|
}
|
||||||
|
{{range $pattern, $name := .Libraries}}
|
||||||
|
{{decapitalise $name}}Addr, _, _, _ := Deploy{{capitalise $name}}(auth, backend)
|
||||||
|
{{$contract.Type}}Bin = strings.Replace({{$contract.Type}}Bin, "__${{$pattern}}$__", {{decapitalise $name}}Addr.String()[2:], -1)
|
||||||
|
{{end}}
|
||||||
|
address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex({{.Type}}Bin), backend {{range .Constructor.Inputs}}, {{.Name}}{{end}})
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, nil, err
|
||||||
|
}
|
||||||
|
return address, tx, &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// {{.Type}} is an auto generated Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}} struct {
|
||||||
|
{{.Type}}Caller // Read-only binding to the contract
|
||||||
|
{{.Type}}Transactor // Write-only binding to the contract
|
||||||
|
{{.Type}}Filterer // Log filterer for contract events
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Caller is an auto generated read-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}Caller struct {
|
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Transactor is an auto generated write-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}Transactor struct {
|
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Filterer is an auto generated log filtering Go binding around an Ethereum contract events.
|
||||||
|
type {{.Type}}Filterer struct {
|
||||||
|
contract *bind.BoundContract // Generic contract wrapper for the low level calls
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Session is an auto generated Go binding around an Ethereum contract,
|
||||||
|
// with pre-set call and transact options.
|
||||||
|
type {{.Type}}Session struct {
|
||||||
|
Contract *{{.Type}} // 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}CallerSession is an auto generated read-only Go binding around an Ethereum contract,
|
||||||
|
// with pre-set call options.
|
||||||
|
type {{.Type}}CallerSession struct {
|
||||||
|
Contract *{{.Type}}Caller // Generic contract caller binding to set the session for
|
||||||
|
CallOpts bind.CallOpts // Call options to use throughout this session
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}TransactorSession is an auto generated write-only Go binding around an Ethereum contract,
|
||||||
|
// with pre-set transact options.
|
||||||
|
type {{.Type}}TransactorSession struct {
|
||||||
|
Contract *{{.Type}}Transactor // Generic contract transactor binding to set the session for
|
||||||
|
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}Raw is an auto generated low-level Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}Raw struct {
|
||||||
|
Contract *{{.Type}} // Generic contract binding to access the raw methods on
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}CallerRaw struct {
|
||||||
|
Contract *{{.Type}}Caller // Generic read-only contract binding to access the raw methods on
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Type}}TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
|
||||||
|
type {{.Type}}TransactorRaw struct {
|
||||||
|
Contract *{{.Type}}Transactor // Generic write-only contract binding to access the raw methods on
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}} creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}(address common.Address, backend bind.ContractBackend) (*{{.Type}}, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, backend, backend, backend)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}{ {{.Type}}Caller: {{.Type}}Caller{contract: contract}, {{.Type}}Transactor: {{.Type}}Transactor{contract: contract}, {{.Type}}Filterer: {{.Type}}Filterer{contract: contract} }, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}}Caller creates a new read-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}Caller(address common.Address, caller bind.ContractCaller) (*{{.Type}}Caller, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, caller, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}Caller{contract: contract}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}}Transactor creates a new write-only instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}Transactor(address common.Address, transactor bind.ContractTransactor) (*{{.Type}}Transactor, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, nil, transactor, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}Transactor{contract: contract}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// New{{.Type}}Filterer creates a new log filterer instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
func New{{.Type}}Filterer(address common.Address, filterer bind.ContractFilterer) (*{{.Type}}Filterer, error) {
|
||||||
|
contract, err := bind{{.Type}}(address, nil, nil, filterer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{.Type}}Filterer{contract: contract}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// bind{{.Type}} binds a generic wrapper to an already deployed contract.
|
||||||
|
func bind{{.Type}}(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
|
||||||
|
parsed, err := abi.JSON(strings.NewReader({{.Type}}ABI))
|
||||||
|
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 (_{{$contract.Type}} *{{$contract.Type}}Raw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
|
||||||
|
return _{{$contract.Type}}.Contract.{{$contract.Type}}Caller.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 (_{{$contract.Type}} *{{$contract.Type}}Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.contract.Transfer(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transact invokes the (paid) contract method with params as input values.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{$contract.Type}}Transactor.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 (_{{$contract.Type}} *{{$contract.Type}}CallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error {
|
||||||
|
return _{{$contract.Type}}.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 (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.contract.Transfer(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transact invokes the (paid) contract method with params as input values.
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.contract.Transact(opts, method, params...)
|
||||||
|
}
|
||||||
|
|
||||||
|
{{range .Calls}}
|
||||||
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Caller) {{.Normalized.Name}}(opts *bind.CallOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} },{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}}{{end}} error) {
|
||||||
|
{{if .Structured}}ret := new(struct{
|
||||||
|
{{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}}
|
||||||
|
{{end}}
|
||||||
|
}){{else}}var (
|
||||||
|
{{range $i, $_ := .Normalized.Outputs}}ret{{$i}} = new({{bindtype .Type $structs}})
|
||||||
|
{{end}}
|
||||||
|
){{end}}
|
||||||
|
out := {{if .Structured}}ret{{else}}{{if eq (len .Normalized.Outputs) 1}}ret0{{else}}&[]interface{}{
|
||||||
|
{{range $i, $_ := .Normalized.Outputs}}ret{{$i}},
|
||||||
|
{{end}}
|
||||||
|
}{{end}}{{end}}
|
||||||
|
err := _{{$contract.Type}}.contract.Call(opts, out, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
return {{if .Structured}}*ret,{{else}}{{range $i, $_ := .Normalized.Outputs}}*ret{{$i}},{{end}}{{end}} err
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}CallerSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) ({{if .Structured}}struct{ {{range .Normalized.Outputs}}{{.Name}} {{bindtype .Type $structs}};{{end}} }, {{else}} {{range .Normalized.Outputs}}{{bindtype .Type $structs}},{{end}} {{end}} error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.CallOpts {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range .Transacts}}
|
||||||
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Transactor) {{.Normalized.Name}}(opts *bind.TransactOpts {{range .Normalized.Inputs}}, {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.contract.Transact(opts, "{{.Original.Name}}" {{range .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Session) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{formatmethod .Original $structs}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) {{.Normalized.Name}}({{range $i, $_ := .Normalized.Inputs}}{{if ne $i 0}},{{end}} {{.Name}} {{bindtype .Type $structs}} {{end}}) (*types.Transaction, error) {
|
||||||
|
return _{{$contract.Type}}.Contract.{{.Normalized.Name}}(&_{{$contract.Type}}.TransactOpts {{range $i, $_ := .Normalized.Inputs}}, {{.Name}}{{end}})
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range .Events}}
|
||||||
|
// {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract.
|
||||||
|
type {{$contract.Type}}{{.Normalized.Name}}Iterator struct {
|
||||||
|
Event *{{$contract.Type}}{{.Normalized.Name}} // 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 *{{$contract.Type}}{{.Normalized.Name}}Iterator) 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({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
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({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
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 returns any retrieval or parsing error occurred during filtering.
|
||||||
|
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Error() error {
|
||||||
|
return it.fail
|
||||||
|
}
|
||||||
|
// Close terminates the iteration process, releasing any pending underlying
|
||||||
|
// resources.
|
||||||
|
func (it *{{$contract.Type}}{{.Normalized.Name}}Iterator) Close() error {
|
||||||
|
it.sub.Unsubscribe()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// {{$contract.Type}}{{.Normalized.Name}} represents a {{.Normalized.Name}} event raised by the {{$contract.Type}} contract.
|
||||||
|
type {{$contract.Type}}{{.Normalized.Name}} struct { {{range .Normalized.Inputs}}
|
||||||
|
{{capitalise .Name}} {{if .Indexed}}{{bindtopictype .Type $structs}}{{else}}{{bindtype .Type $structs}}{{end}}; {{end}}
|
||||||
|
Raw types.Log // Blockchain specific contextual infos
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter{{.Normalized.Name}} is a free log retrieval operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{formatevent .Original $structs}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Filter{{.Normalized.Name}}(opts *bind.FilterOpts{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (*{{$contract.Type}}{{.Normalized.Name}}Iterator, error) {
|
||||||
|
{{range .Normalized.Inputs}}
|
||||||
|
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||||
|
for _, {{.Name}}Item := range {{.Name}} {
|
||||||
|
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
||||||
|
}{{end}}{{end}}
|
||||||
|
|
||||||
|
logs, sub, err := _{{$contract.Type}}.contract.FilterLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &{{$contract.Type}}{{.Normalized.Name}}Iterator{contract: _{{$contract.Type}}.contract, event: "{{.Original.Name}}", logs: logs, sub: sub}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch{{.Normalized.Name}} is a free log subscription operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{formatevent .Original $structs}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Watch{{.Normalized.Name}}(opts *bind.WatchOpts, sink chan<- *{{$contract.Type}}{{.Normalized.Name}}{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}} []{{bindtype .Type $structs}}{{end}}{{end}}) (event.Subscription, error) {
|
||||||
|
{{range .Normalized.Inputs}}
|
||||||
|
{{if .Indexed}}var {{.Name}}Rule []interface{}
|
||||||
|
for _, {{.Name}}Item := range {{.Name}} {
|
||||||
|
{{.Name}}Rule = append({{.Name}}Rule, {{.Name}}Item)
|
||||||
|
}{{end}}{{end}}
|
||||||
|
|
||||||
|
logs, sub, err := _{{$contract.Type}}.contract.WatchLogs(opts, "{{.Original.Name}}"{{range .Normalized.Inputs}}{{if .Indexed}}, {{.Name}}Rule{{end}}{{end}})
|
||||||
|
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({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse{{.Normalized.Name}} is a log parse operation binding the contract event 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{formatevent .Original $structs}}
|
||||||
|
func (_{{$contract.Type}} *{{$contract.Type}}Filterer) Parse{{.Normalized.Name}}(log types.Log) (*{{$contract.Type}}{{.Normalized.Name}}, error) {
|
||||||
|
event := new({{$contract.Type}}{{.Normalized.Name}})
|
||||||
|
if err := _{{$contract.Type}}.contract.UnpackLog(event, "{{.Original.Name}}", log); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return event, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
`
|
||||||
|
|
||||||
|
// tmplSourceJava is the Java source template use to generate the contract binding
|
||||||
|
// based on.
|
||||||
|
const tmplSourceJava = `
|
||||||
|
// This file is an automatically generated Java binding. Do not modify as any
|
||||||
|
// change will likely be lost upon the next re-generation!
|
||||||
|
|
||||||
|
package {{.Package}};
|
||||||
|
|
||||||
|
import org.ethereum.geth.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
{{$structs := .Structs}}
|
||||||
|
{{range $contract := .Contracts}}
|
||||||
|
{{if not .Library}}public {{end}}class {{.Type}} {
|
||||||
|
// ABI is the input ABI used to generate the binding from.
|
||||||
|
public final static String ABI = "{{.InputABI}}";
|
||||||
|
{{if $contract.FuncSigs}}
|
||||||
|
// {{.Type}}FuncSigs maps the 4-byte function signature to its string representation.
|
||||||
|
public final static Map<String, String> {{.Type}}FuncSigs;
|
||||||
|
static {
|
||||||
|
Hashtable<String, String> temp = new Hashtable<String, String>();
|
||||||
|
{{range $strsig, $binsig := .FuncSigs}}temp.put("{{$binsig}}", "{{$strsig}}");
|
||||||
|
{{end}}
|
||||||
|
{{.Type}}FuncSigs = Collections.unmodifiableMap(temp);
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
{{if .InputBin}}
|
||||||
|
// BYTECODE is the compiled bytecode used for deploying new contracts.
|
||||||
|
public final static String BYTECODE = "0x{{.InputBin}}";
|
||||||
|
|
||||||
|
// deploy deploys a new Ethereum contract, binding an instance of {{.Type}} to it.
|
||||||
|
public static {{.Type}} deploy(TransactOpts auth, EthereumClient client{{range .Constructor.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception {
|
||||||
|
Interfaces args = Geth.newInterfaces({{(len .Constructor.Inputs)}});
|
||||||
|
String bytecode = BYTECODE;
|
||||||
|
{{if .Libraries}}
|
||||||
|
|
||||||
|
// "link" contract to dependent libraries by deploying them first.
|
||||||
|
{{range $pattern, $name := .Libraries}}
|
||||||
|
{{capitalise $name}} {{decapitalise $name}}Inst = {{capitalise $name}}.deploy(auth, client);
|
||||||
|
bytecode = bytecode.replace("__${{$pattern}}$__", {{decapitalise $name}}Inst.Address.getHex().substring(2));
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{range $index, $element := .Constructor.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
|
||||||
|
{{end}}
|
||||||
|
return new {{.Type}}(Geth.deployContract(auth, ABI, Geth.decodeFromHex(bytecode), client, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal constructor used by contract deployment.
|
||||||
|
private {{.Type}}(BoundContract deployment) {
|
||||||
|
this.Address = deployment.getAddress();
|
||||||
|
this.Deployer = deployment.getDeployer();
|
||||||
|
this.Contract = deployment;
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// Ethereum address where this contract is located at.
|
||||||
|
public final Address Address;
|
||||||
|
|
||||||
|
// Ethereum transaction in which this contract was deployed (if known!).
|
||||||
|
public final Transaction Deployer;
|
||||||
|
|
||||||
|
// Contract instance bound to a blockchain address.
|
||||||
|
private final BoundContract Contract;
|
||||||
|
|
||||||
|
// Creates a new instance of {{.Type}}, bound to a specific deployed contract.
|
||||||
|
public {{.Type}}(Address address, EthereumClient client) throws Exception {
|
||||||
|
this(Geth.bindContract(address, ABI, client));
|
||||||
|
}
|
||||||
|
|
||||||
|
{{range .Calls}}
|
||||||
|
{{if gt (len .Normalized.Outputs) 1}}
|
||||||
|
// {{capitalise .Normalized.Name}}Results is the output of a call to {{.Normalized.Name}}.
|
||||||
|
public class {{capitalise .Normalized.Name}}Results {
|
||||||
|
{{range $index, $item := .Normalized.Outputs}}public {{bindtype .Type $structs}} {{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}};
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
// {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else}}{{range .Normalized.Outputs}}{{bindtype .Type $structs}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception {
|
||||||
|
Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}});
|
||||||
|
{{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
Interfaces results = Geth.newInterfaces({{(len .Normalized.Outputs)}});
|
||||||
|
{{range $index, $item := .Normalized.Outputs}}Interface result{{$index}} = Geth.newInterface(); result{{$index}}.setDefault{{namedtype (bindtype .Type $structs) .Type}}(); results.set({{$index}}, result{{$index}});
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
if (opts == null) {
|
||||||
|
opts = Geth.newCallOpts();
|
||||||
|
}
|
||||||
|
this.Contract.call(opts, results, "{{.Original.Name}}", args);
|
||||||
|
{{if gt (len .Normalized.Outputs) 1}}
|
||||||
|
{{capitalise .Normalized.Name}}Results result = new {{capitalise .Normalized.Name}}Results();
|
||||||
|
{{range $index, $item := .Normalized.Outputs}}result.{{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}} = results.get({{$index}}).get{{namedtype (bindtype .Type $structs) .Type}}();
|
||||||
|
{{end}}
|
||||||
|
return result;
|
||||||
|
{{else}}{{range .Normalized.Outputs}}return results.get(0).get{{namedtype (bindtype .Type $structs) .Type}}();{{end}}
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range .Transacts}}
|
||||||
|
// {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.ID}}.
|
||||||
|
//
|
||||||
|
// Solidity: {{.Original.String}}
|
||||||
|
public Transaction {{.Normalized.Name}}(TransactOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type $structs}} {{.Name}}{{end}}) throws Exception {
|
||||||
|
Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}});
|
||||||
|
{{range $index, $item := .Normalized.Inputs}}Interface arg{{$index}} = Geth.newInterface();arg{{$index}}.set{{namedtype (bindtype .Type $structs) .Type}}({{.Name}});args.set({{$index}},arg{{$index}});
|
||||||
|
{{end}}
|
||||||
|
return this.Contract.transact(opts, "{{.Original.Name}}" , args);
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
`
|
@ -80,15 +80,19 @@ func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
|
|||||||
copy(topic[:], hash[:])
|
copy(topic[:], hash[:])
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
// todo(rjl493456442) according solidity documentation, indexed event
|
||||||
|
// parameters that are not value types i.e. arrays and structs are not
|
||||||
|
// stored directly but instead a keccak256-hash of an encoding is stored.
|
||||||
|
//
|
||||||
|
// We only convert stringS and bytes to hash, still need to deal with
|
||||||
|
// array(both fixed-size and dynamic-size) and struct.
|
||||||
|
|
||||||
// Attempt to generate the topic from funky types
|
// Attempt to generate the topic from funky types
|
||||||
val := reflect.ValueOf(rule)
|
val := reflect.ValueOf(rule)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
// static byte array
|
// static byte array
|
||||||
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
|
case val.Kind() == reflect.Array && reflect.TypeOf(rule).Elem().Kind() == reflect.Uint8:
|
||||||
reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
|
reflect.Copy(reflect.ValueOf(topic[:val.Len()]), val)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
|
return nil, fmt.Errorf("unsupported indexed type: %T", rule)
|
||||||
}
|
}
|
||||||
@ -162,6 +166,7 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
// Ran out of plain primitive types, try custom types
|
// Ran out of plain primitive types, try custom types
|
||||||
|
|
||||||
switch field.Type() {
|
switch field.Type() {
|
||||||
case reflectHash: // Also covers all dynamic types
|
case reflectHash: // Also covers all dynamic types
|
||||||
field.Set(reflect.ValueOf(topics[0]))
|
field.Set(reflect.ValueOf(topics[0]))
|
||||||
@ -178,11 +183,9 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er
|
|||||||
default:
|
default:
|
||||||
// Ran out of custom types, try the crazies
|
// Ran out of custom types, try the crazies
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
// static byte array
|
// static byte array
|
||||||
case arg.Type.T == abi.FixedBytesTy:
|
case arg.Type.T == abi.FixedBytesTy:
|
||||||
reflect.Copy(field, reflect.ValueOf(topics[0][:arg.Type.Size]))
|
reflect.Copy(field, reflect.ValueOf(topics[0][:arg.Type.Size]))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
|
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
|
||||||
}
|
}
|
103
accounts/abi/bind/topics_test.go
Normal file
103
accounts/abi/bind/topics_test.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// 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
|
||||||
|
// 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 bind
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMakeTopics(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
query [][]interface{}
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want [][]common.Hash
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"support fixed byte types, right padded to 32 bytes",
|
||||||
|
args{[][]interface{}{{[5]byte{1, 2, 3, 4, 5}}}},
|
||||||
|
[][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := makeTopics(tt.args.query...)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("makeTopics() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("makeTopics() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseTopics(t *testing.T) {
|
||||||
|
type bytesStruct struct {
|
||||||
|
StaticBytes [5]byte
|
||||||
|
}
|
||||||
|
bytesType, _ := abi.NewType("bytes5", "", nil)
|
||||||
|
type args struct {
|
||||||
|
createObj func() interface{}
|
||||||
|
resultObj func() interface{}
|
||||||
|
fields abi.Arguments
|
||||||
|
topics []common.Hash
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "support fixed byte types, right padded to 32 bytes",
|
||||||
|
args: args{
|
||||||
|
createObj: func() interface{} { return &bytesStruct{} },
|
||||||
|
resultObj: func() interface{} { return &bytesStruct{StaticBytes: [5]byte{1, 2, 3, 4, 5}} },
|
||||||
|
fields: abi.Arguments{abi.Argument{
|
||||||
|
Name: "staticBytes",
|
||||||
|
Type: bytesType,
|
||||||
|
Indexed: true,
|
||||||
|
}},
|
||||||
|
topics: []common.Hash{
|
||||||
|
{1, 2, 3, 4, 5},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
createObj := tt.args.createObj()
|
||||||
|
if err := parseTopics(createObj, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("parseTopics() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
resultObj := tt.args.resultObj()
|
||||||
|
if !reflect.DeepEqual(createObj, resultObj) {
|
||||||
|
t.Errorf("parseTopics() = %v, want %v", createObj, resultObj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
96
accounts/abi/bind/util_test.go
Normal file
96
accounts/abi/bind/util_test.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// 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 bind_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math/big"
|
||||||
|
"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/core"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||||
|
|
||||||
|
var waitDeployedTests = map[string]struct {
|
||||||
|
code string
|
||||||
|
gas uint64
|
||||||
|
wantAddress common.Address
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
"successful deploy": {
|
||||||
|
code: `6060604052600a8060106000396000f360606040526008565b00`,
|
||||||
|
gas: 3000000,
|
||||||
|
wantAddress: common.HexToAddress("0x3a220f351252089d385b29beca14e27f204c296a"),
|
||||||
|
},
|
||||||
|
"empty code": {
|
||||||
|
code: ``,
|
||||||
|
gas: 300000,
|
||||||
|
wantErr: bind.ErrNoCodeAfterDeploy,
|
||||||
|
wantAddress: common.HexToAddress("0x3a220f351252089d385b29beca14e27f204c296a"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWaitDeployed(t *testing.T) {
|
||||||
|
for name, test := range waitDeployedTests {
|
||||||
|
backend := backends.NewSimulatedBackend(
|
||||||
|
core.GenesisAlloc{
|
||||||
|
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)},
|
||||||
|
},
|
||||||
|
10000000,
|
||||||
|
)
|
||||||
|
defer backend.Close()
|
||||||
|
|
||||||
|
// Create the transaction.
|
||||||
|
tx := types.NewContractCreation(0, big.NewInt(0), test.gas, big.NewInt(1), common.FromHex(test.code))
|
||||||
|
tx, _ = types.SignTx(tx, types.HomesteadSigner{}, testKey)
|
||||||
|
|
||||||
|
// Wait for it to get mined in the background.
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
address common.Address
|
||||||
|
mined = make(chan struct{})
|
||||||
|
ctx = context.Background()
|
||||||
|
)
|
||||||
|
go func() {
|
||||||
|
address, err = bind.WaitDeployed(ctx, backend, tx)
|
||||||
|
close(mined)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Send and mine the transaction.
|
||||||
|
backend.SendTransaction(ctx, tx)
|
||||||
|
backend.Commit()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-mined:
|
||||||
|
if err != test.wantErr {
|
||||||
|
t.Errorf("test %q: error mismatch: got %q, want %q", name, err, test.wantErr)
|
||||||
|
}
|
||||||
|
if address != test.wantAddress {
|
||||||
|
t.Errorf("test %q: unexpected contract address %s", name, address.Hex())
|
||||||
|
}
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Errorf("test %q: timeout", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
accounts/abi/event.go
Normal file
77
accounts/abi/event.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 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 abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event is an event potentially triggered by the EVM's LOG mechanism. The Event
|
||||||
|
// holds type information (inputs) about the yielded output. Anonymous events
|
||||||
|
// don't get the signature canonical representation as the first LOG topic.
|
||||||
|
type Event struct {
|
||||||
|
// Name is the event name used for internal representation. It's derived from
|
||||||
|
// the raw name and a suffix will be added in the case of a event overload.
|
||||||
|
//
|
||||||
|
// e.g.
|
||||||
|
// There are two events have same name:
|
||||||
|
// * foo(int,int)
|
||||||
|
// * foo(uint,uint)
|
||||||
|
// The event name of the first one wll be resolved as foo while the second one
|
||||||
|
// will be resolved as foo0.
|
||||||
|
Name string
|
||||||
|
// RawName is the raw event name parsed from ABI.
|
||||||
|
RawName string
|
||||||
|
Anonymous bool
|
||||||
|
Inputs Arguments
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Event) String() string {
|
||||||
|
inputs := make([]string, len(e.Inputs))
|
||||||
|
for i, input := range e.Inputs {
|
||||||
|
inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name)
|
||||||
|
if input.Indexed {
|
||||||
|
inputs[i] = fmt.Sprintf("%v indexed %v", input.Type, input.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("event %v(%v)", e.RawName, strings.Join(inputs, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sig returns the event string signature according to the ABI spec.
|
||||||
|
//
|
||||||
|
// Example
|
||||||
|
//
|
||||||
|
// event foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||||
|
//
|
||||||
|
// Please note that "int" is substitute for its canonical representation "int256"
|
||||||
|
func (e Event) Sig() string {
|
||||||
|
types := make([]string, len(e.Inputs))
|
||||||
|
for i, input := range e.Inputs {
|
||||||
|
types[i] = input.Type.String()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v(%v)", e.RawName, strings.Join(types, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the canonical representation of the event's signature used by the
|
||||||
|
// abi definition to identify event names and types.
|
||||||
|
func (e Event) ID() common.Hash {
|
||||||
|
return common.BytesToHash(crypto.Keccak256([]byte(e.Sig())))
|
||||||
|
}
|
394
accounts/abi/event_test.go
Normal file
394
accounts/abi/event_test.go
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
// 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 abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var jsonEventTransfer = []byte(`{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"indexed": true, "name": "from", "type": "address"
|
||||||
|
}, {
|
||||||
|
"indexed": true, "name": "to", "type": "address"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "value", "type": "uint256"
|
||||||
|
}],
|
||||||
|
"name": "Transfer",
|
||||||
|
"type": "event"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var jsonEventPledge = []byte(`{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [{
|
||||||
|
"indexed": false, "name": "who", "type": "address"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "wad", "type": "uint128"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "currency", "type": "bytes3"
|
||||||
|
}],
|
||||||
|
"name": "Pledge",
|
||||||
|
"type": "event"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var jsonEventMixedCase = []byte(`{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [{
|
||||||
|
"indexed": false, "name": "value", "type": "uint256"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "_value", "type": "uint256"
|
||||||
|
}, {
|
||||||
|
"indexed": false, "name": "Value", "type": "uint256"
|
||||||
|
}],
|
||||||
|
"name": "MixedCase",
|
||||||
|
"type": "event"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
// 1000000
|
||||||
|
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
|
||||||
|
|
||||||
|
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
|
||||||
|
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
|
||||||
|
|
||||||
|
// 1000000,2218516807680,1000001
|
||||||
|
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
|
||||||
|
|
||||||
|
func TestEventId(t *testing.T) {
|
||||||
|
var table = []struct {
|
||||||
|
definition string
|
||||||
|
expectations map[string]common.Hash
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
definition: `[
|
||||||
|
{ "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
|
||||||
|
{ "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }
|
||||||
|
]`,
|
||||||
|
expectations: map[string]common.Hash{
|
||||||
|
"Balance": crypto.Keccak256Hash([]byte("Balance(uint256)")),
|
||||||
|
"Check": crypto.Keccak256Hash([]byte("Check(address,uint256)")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
abi, err := JSON(strings.NewReader(test.definition))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, event := range abi.Events {
|
||||||
|
if event.ID() != test.expectations[name] {
|
||||||
|
t.Errorf("expected id to be %x, got %x", test.expectations[name], event.ID())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventString(t *testing.T) {
|
||||||
|
var table = []struct {
|
||||||
|
definition string
|
||||||
|
expectations map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
definition: `[
|
||||||
|
{ "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
|
||||||
|
{ "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] },
|
||||||
|
{ "type" : "event", "name" : "Transfer", "inputs": [{ "name": "from", "type": "address", "indexed": true }, { "name": "to", "type": "address", "indexed": true }, { "name": "value", "type": "uint256" }] }
|
||||||
|
]`,
|
||||||
|
expectations: map[string]string{
|
||||||
|
"Balance": "event Balance(uint256 in)",
|
||||||
|
"Check": "event Check(address t, uint256 b)",
|
||||||
|
"Transfer": "event Transfer(address indexed from, address indexed to, uint256 value)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
abi, err := JSON(strings.NewReader(test.definition))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, event := range abi.Events {
|
||||||
|
if event.String() != test.expectations[name] {
|
||||||
|
t.Errorf("expected string to be %s, got %s", test.expectations[name], event.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
|
||||||
|
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
|
||||||
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||||
|
type testStruct struct {
|
||||||
|
Value1 [2]uint8
|
||||||
|
Value2 uint8
|
||||||
|
}
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var b bytes.Buffer
|
||||||
|
var i uint8 = 1
|
||||||
|
for ; i <= 3; i++ {
|
||||||
|
b.Write(packNum(reflect.ValueOf(i)))
|
||||||
|
}
|
||||||
|
var rst testStruct
|
||||||
|
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
||||||
|
require.Equal(t, [2]uint8{1, 2}, rst.Value1)
|
||||||
|
require.Equal(t, uint8(3), rst.Value2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventTupleUnpack(t *testing.T) {
|
||||||
|
|
||||||
|
type EventTransfer struct {
|
||||||
|
Value *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventTransferWithTag struct {
|
||||||
|
// this is valid because `value` is not exportable,
|
||||||
|
// so value is only unmarshalled into `Value1`.
|
||||||
|
value *big.Int //lint:ignore U1000 unused field is part of test
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadEventTransferWithSameFieldAndTag struct {
|
||||||
|
Value *big.Int
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadEventTransferWithDuplicatedTag struct {
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
Value2 *big.Int `abi:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadEventTransferWithEmptyTag struct {
|
||||||
|
Value *big.Int `abi:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventPledge struct {
|
||||||
|
Who common.Address
|
||||||
|
Wad *big.Int
|
||||||
|
Currency [3]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type BadEventPledge struct {
|
||||||
|
Who string
|
||||||
|
Wad int
|
||||||
|
Currency [3]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventMixedCase struct {
|
||||||
|
Value1 *big.Int `abi:"value"`
|
||||||
|
Value2 *big.Int `abi:"_value"`
|
||||||
|
Value3 *big.Int `abi:"Value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
bigint := new(big.Int)
|
||||||
|
bigintExpected := big.NewInt(1000000)
|
||||||
|
bigintExpected2 := big.NewInt(2218516807680)
|
||||||
|
bigintExpected3 := big.NewInt(1000001)
|
||||||
|
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
|
||||||
|
var testCases = []struct {
|
||||||
|
data string
|
||||||
|
dest interface{}
|
||||||
|
expected interface{}
|
||||||
|
jsonLog []byte
|
||||||
|
error string
|
||||||
|
name string
|
||||||
|
}{{
|
||||||
|
transferData1,
|
||||||
|
&EventTransfer{},
|
||||||
|
&EventTransfer{Value: bigintExpected},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"",
|
||||||
|
"Can unpack ERC20 Transfer event into structure",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&[]interface{}{&bigint},
|
||||||
|
&[]interface{}{&bigintExpected},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"",
|
||||||
|
"Can unpack ERC20 Transfer event into slice",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&EventTransferWithTag{},
|
||||||
|
&EventTransferWithTag{Value1: bigintExpected},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"",
|
||||||
|
"Can unpack ERC20 Transfer event into structure with abi: tag",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&BadEventTransferWithDuplicatedTag{},
|
||||||
|
&BadEventTransferWithDuplicatedTag{},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"struct: abi tag in 'Value2' already mapped",
|
||||||
|
"Can not unpack ERC20 Transfer event with duplicated abi tag",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&BadEventTransferWithSameFieldAndTag{},
|
||||||
|
&BadEventTransferWithSameFieldAndTag{},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"abi: multiple variables maps to the same abi field 'value'",
|
||||||
|
"Can not unpack ERC20 Transfer event with a field and a tag mapping to the same abi variable",
|
||||||
|
}, {
|
||||||
|
transferData1,
|
||||||
|
&BadEventTransferWithEmptyTag{},
|
||||||
|
&BadEventTransferWithEmptyTag{},
|
||||||
|
jsonEventTransfer,
|
||||||
|
"struct: abi tag in 'Value' is empty",
|
||||||
|
"Can not unpack ERC20 Transfer event with an empty tag",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&EventPledge{},
|
||||||
|
&EventPledge{
|
||||||
|
addr,
|
||||||
|
bigintExpected2,
|
||||||
|
[3]byte{'u', 's', 'd'}},
|
||||||
|
jsonEventPledge,
|
||||||
|
"",
|
||||||
|
"Can unpack Pledge event into structure",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&[]interface{}{&common.Address{}, &bigint, &[3]byte{}},
|
||||||
|
&[]interface{}{
|
||||||
|
&addr,
|
||||||
|
&bigintExpected2,
|
||||||
|
&[3]byte{'u', 's', 'd'}},
|
||||||
|
jsonEventPledge,
|
||||||
|
"",
|
||||||
|
"Can unpack Pledge event into slice",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&[3]interface{}{&common.Address{}, &bigint, &[3]byte{}},
|
||||||
|
&[3]interface{}{
|
||||||
|
&addr,
|
||||||
|
&bigintExpected2,
|
||||||
|
&[3]byte{'u', 's', 'd'}},
|
||||||
|
jsonEventPledge,
|
||||||
|
"",
|
||||||
|
"Can unpack Pledge event into an array",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&[]interface{}{new(int), 0, 0},
|
||||||
|
&[]interface{}{},
|
||||||
|
jsonEventPledge,
|
||||||
|
"abi: cannot unmarshal common.Address in to int",
|
||||||
|
"Can not unpack Pledge event into slice with wrong types",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&BadEventPledge{},
|
||||||
|
&BadEventPledge{},
|
||||||
|
jsonEventPledge,
|
||||||
|
"abi: cannot unmarshal common.Address in to string",
|
||||||
|
"Can not unpack Pledge event into struct with wrong filed types",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
&[]interface{}{common.Address{}, new(big.Int)},
|
||||||
|
&[]interface{}{},
|
||||||
|
jsonEventPledge,
|
||||||
|
"abi: insufficient number of elements in the list/array for unpack, want 3, got 2",
|
||||||
|
"Can not unpack Pledge event into too short slice",
|
||||||
|
}, {
|
||||||
|
pledgeData1,
|
||||||
|
new(map[string]interface{}),
|
||||||
|
&[]interface{}{},
|
||||||
|
jsonEventPledge,
|
||||||
|
"abi: cannot unmarshal tuple into map[string]interface {}",
|
||||||
|
"Can not unpack Pledge event into map",
|
||||||
|
}, {
|
||||||
|
mixedCaseData1,
|
||||||
|
&EventMixedCase{},
|
||||||
|
&EventMixedCase{Value1: bigintExpected, Value2: bigintExpected2, Value3: bigintExpected3},
|
||||||
|
jsonEventMixedCase,
|
||||||
|
"",
|
||||||
|
"Can unpack abi variables with mixed case",
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
assert := assert.New(t)
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := unpackTestEventData(tc.dest, tc.data, tc.jsonLog, assert)
|
||||||
|
if tc.error == "" {
|
||||||
|
assert.Nil(err, "Should be able to unpack event data.")
|
||||||
|
assert.Equal(tc.expected, tc.dest, tc.name)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(err, tc.error, tc.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, assert *assert.Assertions) error {
|
||||||
|
data, err := hex.DecodeString(hexData)
|
||||||
|
assert.NoError(err, "Hex data should be a correct hex-string")
|
||||||
|
var e Event
|
||||||
|
assert.NoError(json.Unmarshal(jsonEvent, &e), "Should be able to unmarshal event ABI")
|
||||||
|
a := ABI{Events: map[string]Event{"e": e}}
|
||||||
|
return a.Unpack(dest, "e", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEventUnpackIndexed verifies that indexed field will be skipped by event decoder.
|
||||||
|
func TestEventUnpackIndexed(t *testing.T) {
|
||||||
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
|
||||||
|
type testStruct struct {
|
||||||
|
Value1 uint8
|
||||||
|
Value2 uint8
|
||||||
|
}
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var b bytes.Buffer
|
||||||
|
b.Write(packNum(reflect.ValueOf(uint8(8))))
|
||||||
|
var rst testStruct
|
||||||
|
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
||||||
|
require.Equal(t, uint8(0), rst.Value1)
|
||||||
|
require.Equal(t, uint8(8), rst.Value2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEventIndexedWithArrayUnpack verifies that decoder will not overlow when static array is indexed input.
|
||||||
|
func TestEventIndexedWithArrayUnpack(t *testing.T) {
|
||||||
|
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": true, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"string"}]}]`
|
||||||
|
type testStruct struct {
|
||||||
|
Value1 [2]uint8
|
||||||
|
Value2 string
|
||||||
|
}
|
||||||
|
abi, err := JSON(strings.NewReader(definition))
|
||||||
|
require.NoError(t, err)
|
||||||
|
var b bytes.Buffer
|
||||||
|
stringOut := "abc"
|
||||||
|
// number of fields that will be encoded * 32
|
||||||
|
b.Write(packNum(reflect.ValueOf(32)))
|
||||||
|
b.Write(packNum(reflect.ValueOf(len(stringOut))))
|
||||||
|
b.Write(common.RightPadBytes([]byte(stringOut), 32))
|
||||||
|
|
||||||
|
var rst testStruct
|
||||||
|
require.NoError(t, abi.Unpack(&rst, "test", b.Bytes()))
|
||||||
|
require.Equal(t, [2]uint8{0, 0}, rst.Value1)
|
||||||
|
require.Equal(t, stringOut, rst.Value2)
|
||||||
|
}
|
90
accounts/abi/method.go
Normal file
90
accounts/abi/method.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// Copyright 2015 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 abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Method represents a callable given a `Name` and whether the method is a constant.
|
||||||
|
// If the method is `Const` no transaction needs to be created for this
|
||||||
|
// particular Method call. It can easily be simulated using a local VM.
|
||||||
|
// For example a `Balance()` method only needs to retrieve something
|
||||||
|
// from the storage and therefore requires no Tx to be send to the
|
||||||
|
// network. A method such as `Transact` does require a Tx and thus will
|
||||||
|
// be flagged `false`.
|
||||||
|
// Input specifies the required input parameters for this gives method.
|
||||||
|
type Method struct {
|
||||||
|
// Name is the method name used for internal representation. It's derived from
|
||||||
|
// the raw name and a suffix will be added in the case of a function overload.
|
||||||
|
//
|
||||||
|
// e.g.
|
||||||
|
// There are two functions have same name:
|
||||||
|
// * foo(int,int)
|
||||||
|
// * foo(uint,uint)
|
||||||
|
// The method name of the first one will be resolved as foo while the second one
|
||||||
|
// will be resolved as foo0.
|
||||||
|
Name string
|
||||||
|
// RawName is the raw method name parsed from ABI.
|
||||||
|
RawName string
|
||||||
|
Const bool
|
||||||
|
Inputs Arguments
|
||||||
|
Outputs Arguments
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sig returns the methods string signature according to the ABI spec.
|
||||||
|
//
|
||||||
|
// Example
|
||||||
|
//
|
||||||
|
// function foo(uint32 a, int b) = "foo(uint32,int256)"
|
||||||
|
//
|
||||||
|
// Please note that "int" is substitute for its canonical representation "int256"
|
||||||
|
func (method Method) Sig() string {
|
||||||
|
types := make([]string, len(method.Inputs))
|
||||||
|
for i, input := range method.Inputs {
|
||||||
|
types[i] = input.Type.String()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%v(%v)", method.RawName, strings.Join(types, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (method Method) String() string {
|
||||||
|
inputs := make([]string, len(method.Inputs))
|
||||||
|
for i, input := range method.Inputs {
|
||||||
|
inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name)
|
||||||
|
}
|
||||||
|
outputs := make([]string, len(method.Outputs))
|
||||||
|
for i, output := range method.Outputs {
|
||||||
|
outputs[i] = output.Type.String()
|
||||||
|
if len(output.Name) > 0 {
|
||||||
|
outputs[i] += fmt.Sprintf(" %v", output.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
constant := ""
|
||||||
|
if method.Const {
|
||||||
|
constant = "constant "
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.RawName, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the canonical representation of the method's signature used by the
|
||||||
|
// abi definition to identify method names and types.
|
||||||
|
func (method Method) ID() []byte {
|
||||||
|
return crypto.Keccak256([]byte(method.Sig()))[:4]
|
||||||
|
}
|
128
accounts/abi/method_test.go
Normal file
128
accounts/abi/method_test.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Copyright 2018 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 abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const methoddata = `
|
||||||
|
[
|
||||||
|
{"type": "function", "name": "balance", "constant": true },
|
||||||
|
{"type": "function", "name": "send", "constant": false, "inputs": [{ "name": "amount", "type": "uint256" }]},
|
||||||
|
{"type": "function", "name": "transfer", "constant": false, "inputs": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}], "outputs": [{"name": "success", "type": "bool"}]},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple"}],"name":"tuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[]"}],"name":"tupleSlice","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5]"}],"name":"tupleArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},
|
||||||
|
{"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5][]"}],"name":"complexTuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}
|
||||||
|
]`
|
||||||
|
|
||||||
|
func TestMethodString(t *testing.T) {
|
||||||
|
var table = []struct {
|
||||||
|
method string
|
||||||
|
expectation string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
method: "balance",
|
||||||
|
expectation: "function balance() constant returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "send",
|
||||||
|
expectation: "function send(uint256 amount) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "transfer",
|
||||||
|
expectation: "function transfer(address from, address to, uint256 value) returns(bool success)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tuple",
|
||||||
|
expectation: "function tuple((uint256,uint256) a) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleArray",
|
||||||
|
expectation: "function tupleArray((uint256,uint256)[5] a) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleSlice",
|
||||||
|
expectation: "function tupleSlice((uint256,uint256)[] a) returns()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "complexTuple",
|
||||||
|
expectation: "function complexTuple((uint256,uint256)[5][] a) returns()",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
abi, err := JSON(strings.NewReader(methoddata))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range table {
|
||||||
|
got := abi.Methods[test.method].String()
|
||||||
|
if got != test.expectation {
|
||||||
|
t.Errorf("expected string to be %s, got %s", test.expectation, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethodSig(t *testing.T) {
|
||||||
|
var cases = []struct {
|
||||||
|
method string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
method: "balance",
|
||||||
|
expect: "balance()",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "send",
|
||||||
|
expect: "send(uint256)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "transfer",
|
||||||
|
expect: "transfer(address,address,uint256)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tuple",
|
||||||
|
expect: "tuple((uint256,uint256))",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleArray",
|
||||||
|
expect: "tupleArray((uint256,uint256)[5])",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "tupleSlice",
|
||||||
|
expect: "tupleSlice((uint256,uint256)[])",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
method: "complexTuple",
|
||||||
|
expect: "complexTuple((uint256,uint256)[5][])",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
abi, err := JSON(strings.NewReader(methoddata))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range cases {
|
||||||
|
got := abi.Methods[test.method].Sig()
|
||||||
|
if got != test.expect {
|
||||||
|
t.Errorf("expected string to be %s, got %s", test.expect, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
accounts/abi/numbers_test.go
Normal file
33
accounts/abi/numbers_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2015 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 abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNumberTypes(t *testing.T) {
|
||||||
|
ubytes := make([]byte, 32)
|
||||||
|
ubytes[31] = 1
|
||||||
|
|
||||||
|
unsigned := U256(big.NewInt(1))
|
||||||
|
if !bytes.Equal(unsigned, ubytes) {
|
||||||
|
t.Errorf("expected %x got %x", ubytes, unsigned)
|
||||||
|
}
|
||||||
|
}
|
783
accounts/abi/pack_test.go
Normal file
783
accounts/abi/pack_test.go
Normal file
@ -0,0 +1,783 @@
|
|||||||
|
// Copyright 2017 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 abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPack(t *testing.T) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
typ string
|
||||||
|
components []ArgumentMarshaling
|
||||||
|
input interface{}
|
||||||
|
output []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"uint8",
|
||||||
|
nil,
|
||||||
|
uint8(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint8[]",
|
||||||
|
nil,
|
||||||
|
[]uint8{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint16",
|
||||||
|
nil,
|
||||||
|
uint16(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint16[]",
|
||||||
|
nil,
|
||||||
|
[]uint16{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint32",
|
||||||
|
nil,
|
||||||
|
uint32(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint32[]",
|
||||||
|
nil,
|
||||||
|
[]uint32{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint64",
|
||||||
|
nil,
|
||||||
|
uint64(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint64[]",
|
||||||
|
nil,
|
||||||
|
[]uint64{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint256",
|
||||||
|
nil,
|
||||||
|
big.NewInt(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint256[]",
|
||||||
|
nil,
|
||||||
|
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int8",
|
||||||
|
nil,
|
||||||
|
int8(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int8[]",
|
||||||
|
nil,
|
||||||
|
[]int8{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int16",
|
||||||
|
nil,
|
||||||
|
int16(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int16[]",
|
||||||
|
nil,
|
||||||
|
[]int16{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int32",
|
||||||
|
nil,
|
||||||
|
int32(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int32[]",
|
||||||
|
nil,
|
||||||
|
[]int32{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int64",
|
||||||
|
nil,
|
||||||
|
int64(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int64[]",
|
||||||
|
nil,
|
||||||
|
[]int64{1, 2},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int256",
|
||||||
|
nil,
|
||||||
|
big.NewInt(2),
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"int256[]",
|
||||||
|
nil,
|
||||||
|
[]*big.Int{big.NewInt(1), big.NewInt(2)},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes1",
|
||||||
|
nil,
|
||||||
|
[1]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes2",
|
||||||
|
nil,
|
||||||
|
[2]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes3",
|
||||||
|
nil,
|
||||||
|
[3]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes4",
|
||||||
|
nil,
|
||||||
|
[4]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes5",
|
||||||
|
nil,
|
||||||
|
[5]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes6",
|
||||||
|
nil,
|
||||||
|
[6]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes7",
|
||||||
|
nil,
|
||||||
|
[7]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes8",
|
||||||
|
nil,
|
||||||
|
[8]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes9",
|
||||||
|
nil,
|
||||||
|
[9]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes10",
|
||||||
|
nil,
|
||||||
|
[10]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes11",
|
||||||
|
nil,
|
||||||
|
[11]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes12",
|
||||||
|
nil,
|
||||||
|
[12]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes13",
|
||||||
|
nil,
|
||||||
|
[13]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes14",
|
||||||
|
nil,
|
||||||
|
[14]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes15",
|
||||||
|
nil,
|
||||||
|
[15]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes16",
|
||||||
|
nil,
|
||||||
|
[16]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes17",
|
||||||
|
nil,
|
||||||
|
[17]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes18",
|
||||||
|
nil,
|
||||||
|
[18]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes19",
|
||||||
|
nil,
|
||||||
|
[19]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes20",
|
||||||
|
nil,
|
||||||
|
[20]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes21",
|
||||||
|
nil,
|
||||||
|
[21]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes22",
|
||||||
|
nil,
|
||||||
|
[22]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes23",
|
||||||
|
nil,
|
||||||
|
[23]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes24",
|
||||||
|
nil,
|
||||||
|
[24]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes25",
|
||||||
|
nil,
|
||||||
|
[25]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes26",
|
||||||
|
nil,
|
||||||
|
[26]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes27",
|
||||||
|
nil,
|
||||||
|
[27]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes28",
|
||||||
|
nil,
|
||||||
|
[28]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes29",
|
||||||
|
nil,
|
||||||
|
[29]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes30",
|
||||||
|
nil,
|
||||||
|
[30]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes31",
|
||||||
|
nil,
|
||||||
|
[31]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes32",
|
||||||
|
nil,
|
||||||
|
[32]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uint32[2][3][4]",
|
||||||
|
nil,
|
||||||
|
[4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address[]",
|
||||||
|
nil,
|
||||||
|
[]common.Address{{1}, {2}},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes32[]",
|
||||||
|
nil,
|
||||||
|
[]common.Hash{{1}, {2}},
|
||||||
|
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000201000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function",
|
||||||
|
nil,
|
||||||
|
[24]byte{1},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"string",
|
||||||
|
nil,
|
||||||
|
"foobar",
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"string[]",
|
||||||
|
nil,
|
||||||
|
[]string{"hello", "foobar"},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" + // offset 128 to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
|
||||||
|
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
|
||||||
|
"666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"string[2]",
|
||||||
|
nil,
|
||||||
|
[]string{"hello", "foobar"},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset to i = 0
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" + // offset to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
|
||||||
|
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
|
||||||
|
"666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bytes32[][]",
|
||||||
|
nil,
|
||||||
|
[][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"bytes32[][2]",
|
||||||
|
nil,
|
||||||
|
[][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"bytes32[3][2]",
|
||||||
|
nil,
|
||||||
|
[][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}},
|
||||||
|
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[0][2]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// static tuple
|
||||||
|
"tuple",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "int64"},
|
||||||
|
{Name: "b", Type: "int256"},
|
||||||
|
{Name: "c", Type: "int256"},
|
||||||
|
{Name: "d", Type: "bool"},
|
||||||
|
{Name: "e", Type: "bytes32[3][2]"},
|
||||||
|
},
|
||||||
|
struct {
|
||||||
|
A int64
|
||||||
|
B *big.Int
|
||||||
|
C *big.Int
|
||||||
|
D bool
|
||||||
|
E [][]common.Hash
|
||||||
|
}{1, big.NewInt(1), big.NewInt(-1), true, [][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}}},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001" + // struct[a]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // struct[c]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[d]
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][0]
|
||||||
|
"0200000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][1]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][2]
|
||||||
|
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][0]
|
||||||
|
"0400000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][1]
|
||||||
|
"0500000000000000000000000000000000000000000000000000000000000000"), // struct[e] array[1][2]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// dynamic tuple
|
||||||
|
"tuple",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "string"},
|
||||||
|
{Name: "b", Type: "int64"},
|
||||||
|
{Name: "c", Type: "bytes"},
|
||||||
|
{Name: "d", Type: "string[]"},
|
||||||
|
{Name: "e", Type: "int256[]"},
|
||||||
|
{Name: "f", Type: "address[]"},
|
||||||
|
},
|
||||||
|
struct {
|
||||||
|
FieldA string `abi:"a"` // Test whether abi tag works
|
||||||
|
FieldB int64 `abi:"b"`
|
||||||
|
C []byte
|
||||||
|
D []string
|
||||||
|
E []*big.Int
|
||||||
|
F []common.Address
|
||||||
|
}{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}},
|
||||||
|
common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000220" + // struct[e] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000280" + // struct[f] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000006" + // struct[a] length
|
||||||
|
"666f6f6261720000000000000000000000000000000000000000000000000000" + // struct[a] "foobar"
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[c] length
|
||||||
|
"0100000000000000000000000000000000000000000000000000000000000000" + // []byte{1}
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[d] length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // foo offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000080" + // bar offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // foo length
|
||||||
|
"666f6f0000000000000000000000000000000000000000000000000000000000" + // foo
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000003" + // bar offset
|
||||||
|
"6261720000000000000000000000000000000000000000000000000000000000" + // bar
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[e] length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // 1
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // -1
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[f] length
|
||||||
|
"0000000000000000000000000100000000000000000000000000000000000000" + // common.Address{1}
|
||||||
|
"0000000000000000000000000200000000000000000000000000000000000000"), // common.Address{2}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// nested tuple
|
||||||
|
"tuple",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "tuple", Components: []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256[]"}}},
|
||||||
|
{Name: "b", Type: "int256[]"},
|
||||||
|
},
|
||||||
|
struct {
|
||||||
|
A struct {
|
||||||
|
FieldA *big.Int `abi:"a"`
|
||||||
|
B []*big.Int
|
||||||
|
}
|
||||||
|
B []*big.Int
|
||||||
|
}{
|
||||||
|
A: struct {
|
||||||
|
FieldA *big.Int `abi:"a"` // Test whether abi tag works for nested tuple
|
||||||
|
B []*big.Int
|
||||||
|
}{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(0)}},
|
||||||
|
B: []*big.Int{big.NewInt(1), big.NewInt(0)}},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // a offset
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000e0" + // b offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // a.a value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // a.b length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // a.b[0] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000" + // a.b[1] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // b length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // b[0] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000"), // b[1] value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// tuple slice
|
||||||
|
"tuple[]",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "int256"},
|
||||||
|
{Name: "b", Type: "int256[]"},
|
||||||
|
},
|
||||||
|
[]struct {
|
||||||
|
A *big.Int
|
||||||
|
B []*big.Int
|
||||||
|
}{
|
||||||
|
{big.NewInt(-1), []*big.Int{big.NewInt(1), big.NewInt(0)}},
|
||||||
|
{big.NewInt(1), []*big.Int{big.NewInt(2), big.NewInt(-1)}},
|
||||||
|
},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // tuple length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000e0" + // tuple[1] offset
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0].B offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].B length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].B[0] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000" + // tuple[0].B[1] value
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[1].B offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B[0] value
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].B[1] value
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// static tuple array
|
||||||
|
"tuple[2]",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "int256"},
|
||||||
|
{Name: "b", Type: "int256"},
|
||||||
|
},
|
||||||
|
[2]struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{
|
||||||
|
{big.NewInt(-1), big.NewInt(1)},
|
||||||
|
{big.NewInt(1), big.NewInt(-1)},
|
||||||
|
},
|
||||||
|
common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].a
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].b
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].a
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].b
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// dynamic tuple array
|
||||||
|
"tuple[2]",
|
||||||
|
[]ArgumentMarshaling{
|
||||||
|
{Name: "a", Type: "int256[]"},
|
||||||
|
},
|
||||||
|
[2]struct {
|
||||||
|
A []*big.Int
|
||||||
|
}{
|
||||||
|
{[]*big.Int{big.NewInt(-1), big.NewInt(1)}},
|
||||||
|
{[]*big.Int{big.NewInt(1), big.NewInt(-1)}},
|
||||||
|
},
|
||||||
|
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000c0" + // tuple[1] offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[0].A offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].A length
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A[0]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].A[1]
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[1].A offset
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].A length
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A[0]
|
||||||
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].A[1]
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
typ, err := NewType(test.typ, "", test.components)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v failed. Unexpected parse error: %v", i, err)
|
||||||
|
}
|
||||||
|
output, err := typ.pack(reflect.ValueOf(test.input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v failed. Unexpected pack error: %v", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(output, test.output) {
|
||||||
|
t.Errorf("input %d for typ: %v failed. Expected bytes: '%x' Got: '%x'", i, typ.String(), test.output, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMethodPack(t *testing.T) {
|
||||||
|
abi, err := JSON(strings.NewReader(jsondata2))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := abi.Methods["slice"].ID()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
|
||||||
|
packed, err := abi.Pack("slice", []uint32{1, 2})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
var addrA, addrB = common.Address{1}, common.Address{2}
|
||||||
|
sig = abi.Methods["sliceAddress"].ID()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{32}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
||||||
|
|
||||||
|
packed, err = abi.Pack("sliceAddress", []common.Address{addrA, addrB})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
var addrC, addrD = common.Address{3}, common.Address{4}
|
||||||
|
sig = abi.Methods["sliceMultiAddress"].ID()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{64}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{160}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrA[:], 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrB[:], 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrC[:], 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrD[:], 32)...)
|
||||||
|
|
||||||
|
packed, err = abi.Pack("sliceMultiAddress", []common.Address{addrA, addrB}, []common.Address{addrC, addrD})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig = abi.Methods["slice256"].ID()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
|
||||||
|
packed, err = abi.Pack("slice256", []*big.Int{big.NewInt(1), big.NewInt(2)})
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
a := [2][2]*big.Int{{big.NewInt(1), big.NewInt(1)}, {big.NewInt(2), big.NewInt(0)}}
|
||||||
|
sig = abi.Methods["nestedArray"].ID()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrC[:], 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes(addrD[:], 32)...)
|
||||||
|
packed, err = abi.Pack("nestedArray", a, []common.Address{addrC, addrD})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig = abi.Methods["nestedArray2"].ID()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x80}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
packed, err = abi.Pack("nestedArray2", [2][]uint8{{1}, {1}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
|
||||||
|
sig = abi.Methods["nestedSlice"].ID()
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x02}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
|
||||||
|
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
|
||||||
|
packed, err = abi.Pack("nestedSlice", [][]uint8{{1, 2}, {1, 2}})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(packed, sig) {
|
||||||
|
t.Errorf("expected %x got %x", sig, packed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPackNumber(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
value reflect.Value
|
||||||
|
packed []byte
|
||||||
|
}{
|
||||||
|
// Protocol limits
|
||||||
|
{reflect.ValueOf(0), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")},
|
||||||
|
{reflect.ValueOf(1), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")},
|
||||||
|
{reflect.ValueOf(-1), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")},
|
||||||
|
|
||||||
|
// Type corner cases
|
||||||
|
{reflect.ValueOf(uint8(math.MaxUint8)), common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000ff")},
|
||||||
|
{reflect.ValueOf(uint16(math.MaxUint16)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000ffff")},
|
||||||
|
{reflect.ValueOf(uint32(math.MaxUint32)), common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000ffffffff")},
|
||||||
|
{reflect.ValueOf(uint64(math.MaxUint64)), common.Hex2Bytes("000000000000000000000000000000000000000000000000ffffffffffffffff")},
|
||||||
|
|
||||||
|
{reflect.ValueOf(int8(math.MaxInt8)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000007f")},
|
||||||
|
{reflect.ValueOf(int16(math.MaxInt16)), common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000007fff")},
|
||||||
|
{reflect.ValueOf(int32(math.MaxInt32)), common.Hex2Bytes("000000000000000000000000000000000000000000000000000000007fffffff")},
|
||||||
|
{reflect.ValueOf(int64(math.MaxInt64)), common.Hex2Bytes("0000000000000000000000000000000000000000000000007fffffffffffffff")},
|
||||||
|
|
||||||
|
{reflect.ValueOf(int8(math.MinInt8)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80")},
|
||||||
|
{reflect.ValueOf(int16(math.MinInt16)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000")},
|
||||||
|
{reflect.ValueOf(int32(math.MinInt32)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffff80000000")},
|
||||||
|
{reflect.ValueOf(int64(math.MinInt64)), common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffff8000000000000000")},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
packed := packNum(tt.value)
|
||||||
|
if !bytes.Equal(packed, tt.packed) {
|
||||||
|
t.Errorf("test %d: pack mismatch: have %x, want %x", i, packed, tt.packed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,14 @@ func indirect(v reflect.Value) reflect.Value {
|
|||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// indirectInterfaceOrPtr recursively dereferences the value until value is not interface.
|
||||||
|
func indirectInterfaceOrPtr(v reflect.Value) reflect.Value {
|
||||||
|
if (v.Kind() == reflect.Interface || v.Kind() == reflect.Ptr) && v.Elem().IsValid() {
|
||||||
|
return indirect(v.Elem())
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
// reflectIntKind returns the reflect using the given size and
|
// reflectIntKind returns the reflect using the given size and
|
||||||
// unsignedness.
|
// unsignedness.
|
||||||
func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type) {
|
func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type) {
|
191
accounts/abi/reflect_test.go
Normal file
191
accounts/abi/reflect_test.go
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// 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
|
||||||
|
// 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 abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type reflectTest struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
struc interface{}
|
||||||
|
want map[string]string
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
var reflectTests = []reflectTest{
|
||||||
|
{
|
||||||
|
name: "OneToOneCorrespondance",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MissingFieldsInStruct",
|
||||||
|
args: []string{"fieldA", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MoreFieldsInStructThanArgs",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MissingFieldInArgs",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
err: "struct: abi tag 'fieldB' defined but not found in abi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoAbiDescriptor",
|
||||||
|
args: []string{"fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoArgs",
|
||||||
|
args: []string{},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
}{},
|
||||||
|
err: "struct: abi tag 'fieldA' defined but not found in abi",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DifferentName",
|
||||||
|
args: []string{"fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldB": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DifferentName",
|
||||||
|
args: []string{"fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldB": "FieldA",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MultipleFields",
|
||||||
|
args: []string{"fieldA", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
"fieldB": "FieldB",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MultipleFieldsABIMissing",
|
||||||
|
args: []string{"fieldA", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldA"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
want: map[string]string{
|
||||||
|
"fieldA": "FieldA",
|
||||||
|
"fieldB": "FieldB",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NameConflict",
|
||||||
|
args: []string{"fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldB"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
err: "abi: multiple variables maps to the same abi field 'fieldB'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Underscored",
|
||||||
|
args: []string{"_"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int
|
||||||
|
}{},
|
||||||
|
err: "abi: purely underscored output cannot unpack to struct",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DoubleMapping",
|
||||||
|
args: []string{"fieldB", "fieldC", "fieldA"},
|
||||||
|
struc: struct {
|
||||||
|
FieldA int `abi:"fieldC"`
|
||||||
|
FieldB int
|
||||||
|
}{},
|
||||||
|
err: "abi: multiple outputs mapping to the same struct field 'FieldA'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AlreadyMapped",
|
||||||
|
args: []string{"fieldB", "fieldB"},
|
||||||
|
struc: struct {
|
||||||
|
FieldB int `abi:"fieldB"`
|
||||||
|
}{},
|
||||||
|
err: "struct: abi tag in 'FieldB' already mapped",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReflectNameToStruct(t *testing.T) {
|
||||||
|
for _, test := range reflectTests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))
|
||||||
|
if len(test.err) > 0 {
|
||||||
|
if err == nil || err.Error() != test.err {
|
||||||
|
t.Fatalf("Invalid error: expected %v, got %v", test.err, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
for fname := range test.want {
|
||||||
|
if m[fname] != test.want[fname] {
|
||||||
|
t.Fatalf("Incorrect value for field %s: expected %v, got %v", fname, test.want[fname], m[fname])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
365
accounts/abi/type.go
Normal file
365
accounts/abi/type.go
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
// Copyright 2015 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 abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type enumerator
|
||||||
|
const (
|
||||||
|
IntTy byte = iota
|
||||||
|
UintTy
|
||||||
|
BoolTy
|
||||||
|
StringTy
|
||||||
|
SliceTy
|
||||||
|
ArrayTy
|
||||||
|
TupleTy
|
||||||
|
AddressTy
|
||||||
|
FixedBytesTy
|
||||||
|
BytesTy
|
||||||
|
HashTy
|
||||||
|
FixedPointTy
|
||||||
|
FunctionTy
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type is the reflection of the supported argument type
|
||||||
|
type Type struct {
|
||||||
|
Elem *Type
|
||||||
|
Kind reflect.Kind
|
||||||
|
Type reflect.Type
|
||||||
|
Size int
|
||||||
|
T byte // Our own type checking
|
||||||
|
|
||||||
|
stringKind string // holds the unparsed string for deriving signatures
|
||||||
|
|
||||||
|
// Tuple relative fields
|
||||||
|
TupleRawName string // Raw struct name defined in source code, may be empty.
|
||||||
|
TupleElems []*Type // Type information of all tuple fields
|
||||||
|
TupleRawNames []string // Raw field name of all tuple fields
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// typeRegex parses the abi sub types
|
||||||
|
typeRegex = regexp.MustCompile("([a-zA-Z]+)(([0-9]+)(x([0-9]+))?)?")
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewType creates a new reflection type of abi type given in t.
|
||||||
|
func NewType(t string, internalType string, components []ArgumentMarshaling) (typ Type, err error) {
|
||||||
|
// check that array brackets are equal if they exist
|
||||||
|
if strings.Count(t, "[") != strings.Count(t, "]") {
|
||||||
|
return Type{}, fmt.Errorf("invalid arg type in abi")
|
||||||
|
}
|
||||||
|
typ.stringKind = t
|
||||||
|
|
||||||
|
// if there are brackets, get ready to go into slice/array mode and
|
||||||
|
// recursively create the type
|
||||||
|
if strings.Count(t, "[") != 0 {
|
||||||
|
// Note internalType can be empty here.
|
||||||
|
subInternal := internalType
|
||||||
|
if i := strings.LastIndex(internalType, "["); i != -1 {
|
||||||
|
subInternal = subInternal[:i]
|
||||||
|
}
|
||||||
|
// recursively embed the type
|
||||||
|
i := strings.LastIndex(t, "[")
|
||||||
|
embeddedType, err := NewType(t[:i], subInternal, components)
|
||||||
|
if err != nil {
|
||||||
|
return Type{}, err
|
||||||
|
}
|
||||||
|
// grab the last cell and create a type from there
|
||||||
|
sliced := t[i:]
|
||||||
|
// grab the slice size with regexp
|
||||||
|
re := regexp.MustCompile("[0-9]+")
|
||||||
|
intz := re.FindAllString(sliced, -1)
|
||||||
|
|
||||||
|
if len(intz) == 0 {
|
||||||
|
// is a slice
|
||||||
|
typ.T = SliceTy
|
||||||
|
typ.Kind = reflect.Slice
|
||||||
|
typ.Elem = &embeddedType
|
||||||
|
typ.Type = reflect.SliceOf(embeddedType.Type)
|
||||||
|
typ.stringKind = embeddedType.stringKind + sliced
|
||||||
|
} else if len(intz) == 1 {
|
||||||
|
// is a array
|
||||||
|
typ.T = ArrayTy
|
||||||
|
typ.Kind = reflect.Array
|
||||||
|
typ.Elem = &embeddedType
|
||||||
|
typ.Size, err = strconv.Atoi(intz[0])
|
||||||
|
if err != nil {
|
||||||
|
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
||||||
|
}
|
||||||
|
typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type)
|
||||||
|
typ.stringKind = embeddedType.stringKind + sliced
|
||||||
|
} else {
|
||||||
|
return Type{}, fmt.Errorf("invalid formatting of array type")
|
||||||
|
}
|
||||||
|
return typ, err
|
||||||
|
}
|
||||||
|
// parse the type and size of the abi-type.
|
||||||
|
matches := typeRegex.FindAllStringSubmatch(t, -1)
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return Type{}, fmt.Errorf("invalid type '%v'", t)
|
||||||
|
}
|
||||||
|
parsedType := matches[0]
|
||||||
|
|
||||||
|
// varSize is the size of the variable
|
||||||
|
var varSize int
|
||||||
|
if len(parsedType[3]) > 0 {
|
||||||
|
var err error
|
||||||
|
varSize, err = strconv.Atoi(parsedType[2])
|
||||||
|
if err != nil {
|
||||||
|
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if parsedType[0] == "uint" || parsedType[0] == "int" {
|
||||||
|
// this should fail because it means that there's something wrong with
|
||||||
|
// the abi type (the compiler should always format it to the size...always)
|
||||||
|
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// varType is the parsed abi type
|
||||||
|
switch varType := parsedType[1]; varType {
|
||||||
|
case "int":
|
||||||
|
typ.Kind, typ.Type = reflectIntKindAndType(false, varSize)
|
||||||
|
typ.Size = varSize
|
||||||
|
typ.T = IntTy
|
||||||
|
case "uint":
|
||||||
|
typ.Kind, typ.Type = reflectIntKindAndType(true, varSize)
|
||||||
|
typ.Size = varSize
|
||||||
|
typ.T = UintTy
|
||||||
|
case "bool":
|
||||||
|
typ.Kind = reflect.Bool
|
||||||
|
typ.T = BoolTy
|
||||||
|
typ.Type = reflect.TypeOf(bool(false))
|
||||||
|
case "address":
|
||||||
|
typ.Kind = reflect.Array
|
||||||
|
typ.Type = addressT
|
||||||
|
typ.Size = 20
|
||||||
|
typ.T = AddressTy
|
||||||
|
case "string":
|
||||||
|
typ.Kind = reflect.String
|
||||||
|
typ.Type = reflect.TypeOf("")
|
||||||
|
typ.T = StringTy
|
||||||
|
case "bytes":
|
||||||
|
if varSize == 0 {
|
||||||
|
typ.T = BytesTy
|
||||||
|
typ.Kind = reflect.Slice
|
||||||
|
typ.Type = reflect.SliceOf(reflect.TypeOf(byte(0)))
|
||||||
|
} else {
|
||||||
|
typ.T = FixedBytesTy
|
||||||
|
typ.Kind = reflect.Array
|
||||||
|
typ.Size = varSize
|
||||||
|
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
|
||||||
|
}
|
||||||
|
case "tuple":
|
||||||
|
var (
|
||||||
|
fields []reflect.StructField
|
||||||
|
elems []*Type
|
||||||
|
names []string
|
||||||
|
expression string // canonical parameter expression
|
||||||
|
)
|
||||||
|
expression += "("
|
||||||
|
for idx, c := range components {
|
||||||
|
cType, err := NewType(c.Type, c.InternalType, c.Components)
|
||||||
|
if err != nil {
|
||||||
|
return Type{}, err
|
||||||
|
}
|
||||||
|
if ToCamelCase(c.Name) == "" {
|
||||||
|
return Type{}, errors.New("abi: purely anonymous or underscored field is not supported")
|
||||||
|
}
|
||||||
|
fields = append(fields, reflect.StructField{
|
||||||
|
Name: ToCamelCase(c.Name), // reflect.StructOf will panic for any exported field.
|
||||||
|
Type: cType.Type,
|
||||||
|
Tag: reflect.StructTag("json:\"" + c.Name + "\""),
|
||||||
|
})
|
||||||
|
elems = append(elems, &cType)
|
||||||
|
names = append(names, c.Name)
|
||||||
|
expression += cType.stringKind
|
||||||
|
if idx != len(components)-1 {
|
||||||
|
expression += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expression += ")"
|
||||||
|
typ.Kind = reflect.Struct
|
||||||
|
typ.Type = reflect.StructOf(fields)
|
||||||
|
typ.TupleElems = elems
|
||||||
|
typ.TupleRawNames = names
|
||||||
|
typ.T = TupleTy
|
||||||
|
typ.stringKind = expression
|
||||||
|
|
||||||
|
const structPrefix = "struct "
|
||||||
|
// After solidity 0.5.10, a new field of abi "internalType"
|
||||||
|
// is introduced. From that we can obtain the struct name
|
||||||
|
// user defined in the source code.
|
||||||
|
if internalType != "" && strings.HasPrefix(internalType, structPrefix) {
|
||||||
|
// Foo.Bar type definition is not allowed in golang,
|
||||||
|
// convert the format to FooBar
|
||||||
|
typ.TupleRawName = strings.Replace(internalType[len(structPrefix):], ".", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "function":
|
||||||
|
typ.Kind = reflect.Array
|
||||||
|
typ.T = FunctionTy
|
||||||
|
typ.Size = 24
|
||||||
|
typ.Type = reflect.ArrayOf(24, reflect.TypeOf(byte(0)))
|
||||||
|
default:
|
||||||
|
return Type{}, fmt.Errorf("unsupported arg type: %s", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// String implements Stringer
|
||||||
|
func (t Type) String() (out string) {
|
||||||
|
return t.stringKind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Type) pack(v reflect.Value) ([]byte, error) {
|
||||||
|
// dereference pointer first if it's a pointer
|
||||||
|
v = indirect(v)
|
||||||
|
if err := typeCheck(t, v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t.T {
|
||||||
|
case SliceTy, ArrayTy:
|
||||||
|
var ret []byte
|
||||||
|
|
||||||
|
if t.requiresLengthPrefix() {
|
||||||
|
// append length
|
||||||
|
ret = append(ret, packNum(reflect.ValueOf(v.Len()))...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate offset if any
|
||||||
|
offset := 0
|
||||||
|
offsetReq := isDynamicType(*t.Elem)
|
||||||
|
if offsetReq {
|
||||||
|
offset = getTypeSize(*t.Elem) * v.Len()
|
||||||
|
}
|
||||||
|
var tail []byte
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
val, err := t.Elem.pack(v.Index(i))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !offsetReq {
|
||||||
|
ret = append(ret, val...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
||||||
|
offset += len(val)
|
||||||
|
tail = append(tail, val...)
|
||||||
|
}
|
||||||
|
return append(ret, tail...), nil
|
||||||
|
case TupleTy:
|
||||||
|
// (T1,...,Tk) for k >= 0 and any types T1, …, Tk
|
||||||
|
// enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))
|
||||||
|
// where X = (X(1), ..., X(k)) and head and tail are defined for Ti being a static
|
||||||
|
// type as
|
||||||
|
// head(X(i)) = enc(X(i)) and tail(X(i)) = "" (the empty string)
|
||||||
|
// and as
|
||||||
|
// head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1))))
|
||||||
|
// tail(X(i)) = enc(X(i))
|
||||||
|
// otherwise, i.e. if Ti is a dynamic type.
|
||||||
|
fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Calculate prefix occupied size.
|
||||||
|
offset := 0
|
||||||
|
for _, elem := range t.TupleElems {
|
||||||
|
offset += getTypeSize(*elem)
|
||||||
|
}
|
||||||
|
var ret, tail []byte
|
||||||
|
for i, elem := range t.TupleElems {
|
||||||
|
field := v.FieldByName(fieldmap[t.TupleRawNames[i]])
|
||||||
|
if !field.IsValid() {
|
||||||
|
return nil, fmt.Errorf("field %s for tuple not found in the given struct", t.TupleRawNames[i])
|
||||||
|
}
|
||||||
|
val, err := elem.pack(field)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if isDynamicType(*elem) {
|
||||||
|
ret = append(ret, packNum(reflect.ValueOf(offset))...)
|
||||||
|
tail = append(tail, val...)
|
||||||
|
offset += len(val)
|
||||||
|
} else {
|
||||||
|
ret = append(ret, val...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(ret, tail...), nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return packElement(t, v), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// requireLengthPrefix returns whether the type requires any sort of length
|
||||||
|
// prefixing.
|
||||||
|
func (t Type) requiresLengthPrefix() bool {
|
||||||
|
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDynamicType returns true if the type is dynamic.
|
||||||
|
// The following types are called “dynamic”:
|
||||||
|
// * bytes
|
||||||
|
// * string
|
||||||
|
// * T[] for any T
|
||||||
|
// * T[k] for any dynamic T and any k >= 0
|
||||||
|
// * (T1,...,Tk) if Ti is dynamic for some 1 <= i <= k
|
||||||
|
func isDynamicType(t Type) bool {
|
||||||
|
if t.T == TupleTy {
|
||||||
|
for _, elem := range t.TupleElems {
|
||||||
|
if isDynamicType(*elem) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy || (t.T == ArrayTy && isDynamicType(*t.Elem))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTypeSize returns the size that this type needs to occupy.
|
||||||
|
// We distinguish static and dynamic types. Static types are encoded in-place
|
||||||
|
// and dynamic types are encoded at a separately allocated location after the
|
||||||
|
// current block.
|
||||||
|
// So for a static variable, the size returned represents the size that the
|
||||||
|
// variable actually occupies.
|
||||||
|
// For a dynamic variable, the returned size is fixed 32 bytes, which is used
|
||||||
|
// to store the location reference for actual value storage.
|
||||||
|
func getTypeSize(t Type) int {
|
||||||
|
if t.T == ArrayTy && !isDynamicType(*t.Elem) {
|
||||||
|
// Recursively calculate type size if it is a nested array
|
||||||
|
if t.Elem.T == ArrayTy {
|
||||||
|
return t.Size * getTypeSize(*t.Elem)
|
||||||
|
}
|
||||||
|
return t.Size * 32
|
||||||
|
} else if t.T == TupleTy && !isDynamicType(t) {
|
||||||
|
total := 0
|
||||||
|
for _, elem := range t.TupleElems {
|
||||||
|
total += getTypeSize(*elem)
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
return 32
|
||||||
|
}
|
308
accounts/abi/type_test.go
Normal file
308
accounts/abi/type_test.go
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
// 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 abi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
// typeWithoutStringer is a alias for the Type type which simply doesn't implement
|
||||||
|
// the stringer interface to allow printing type details in the tests below.
|
||||||
|
type typeWithoutStringer Type
|
||||||
|
|
||||||
|
// Tests that all allowed types get recognized by the type parser.
|
||||||
|
func TestTypeRegexp(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
blob string
|
||||||
|
components []ArgumentMarshaling
|
||||||
|
kind Type
|
||||||
|
}{
|
||||||
|
{"bool", nil, Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}},
|
||||||
|
{"bool[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool(nil)), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}},
|
||||||
|
{"bool[2]", nil, Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}},
|
||||||
|
{"bool[2][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}},
|
||||||
|
{"bool[][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}},
|
||||||
|
{"bool[][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}},
|
||||||
|
{"bool[2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}},
|
||||||
|
{"bool[2][][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][][2]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}},
|
||||||
|
{"bool[2][2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}},
|
||||||
|
{"bool[][][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}},
|
||||||
|
{"bool[][2][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][2][]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}},
|
||||||
|
{"int8", nil, Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}},
|
||||||
|
{"int16", nil, Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}},
|
||||||
|
{"int32", nil, Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}},
|
||||||
|
{"int64", nil, Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}},
|
||||||
|
{"int256", nil, Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}},
|
||||||
|
{"int8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
|
||||||
|
{"int8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
|
||||||
|
{"int16[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
|
||||||
|
{"int16[2]", nil, Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
|
||||||
|
{"int32[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
|
||||||
|
{"int32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
|
||||||
|
{"int64[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
|
||||||
|
{"int64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
|
||||||
|
{"int256[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
|
||||||
|
{"int256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
|
||||||
|
{"uint8", nil, Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}},
|
||||||
|
{"uint16", nil, Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}},
|
||||||
|
{"uint32", nil, Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}},
|
||||||
|
{"uint64", nil, Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}},
|
||||||
|
{"uint256", nil, Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}},
|
||||||
|
{"uint8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
|
||||||
|
{"uint8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
|
||||||
|
{"uint16[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
|
||||||
|
{"uint16[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
|
||||||
|
{"uint32[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
|
||||||
|
{"uint32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
|
||||||
|
{"uint64[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
|
||||||
|
{"uint64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
|
||||||
|
{"uint256[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
|
||||||
|
{"uint256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
|
||||||
|
{"bytes32", nil, Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}},
|
||||||
|
{"bytes[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]byte{}), Elem: &Type{Kind: reflect.Slice, Type: reflect.TypeOf([]byte{}), T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
|
||||||
|
{"bytes[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]byte{}), Elem: &Type{T: BytesTy, Type: reflect.TypeOf([]byte{}), Kind: reflect.Slice, stringKind: "bytes"}, stringKind: "bytes[2]"}},
|
||||||
|
{"bytes32[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][32]byte{}), Elem: &Type{Kind: reflect.Array, Type: reflect.TypeOf([32]byte{}), T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}},
|
||||||
|
{"bytes32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][32]byte{}), Elem: &Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}, stringKind: "bytes32[2]"}},
|
||||||
|
{"string", nil, Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}},
|
||||||
|
{"string[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]string{}), Elem: &Type{Kind: reflect.String, Type: reflect.TypeOf(""), T: StringTy, stringKind: "string"}, stringKind: "string[]"}},
|
||||||
|
{"string[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]string{}), Elem: &Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}, stringKind: "string[2]"}},
|
||||||
|
{"address", nil, Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}},
|
||||||
|
{"address[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
|
||||||
|
{"address[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
|
||||||
|
// TODO when fixed types are implemented properly
|
||||||
|
// {"fixed", nil, Type{}},
|
||||||
|
// {"fixed128x128", nil, Type{}},
|
||||||
|
// {"fixed[]", nil, Type{}},
|
||||||
|
// {"fixed[2]", nil, Type{}},
|
||||||
|
// {"fixed128x128[]", nil, Type{}},
|
||||||
|
// {"fixed128x128[2]", nil, Type{}},
|
||||||
|
{"tuple", []ArgumentMarshaling{{Name: "a", Type: "int64"}}, Type{Kind: reflect.Struct, T: TupleTy, Type: reflect.TypeOf(struct {
|
||||||
|
A int64 `json:"a"`
|
||||||
|
}{}), stringKind: "(int64)",
|
||||||
|
TupleElems: []*Type{{Kind: reflect.Int64, T: IntTy, Type: reflect.TypeOf(int64(0)), Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"a"}}},
|
||||||
|
{"tuple with long name", []ArgumentMarshaling{{Name: "aTypicalParamName", Type: "int64"}}, Type{Kind: reflect.Struct, T: TupleTy, Type: reflect.TypeOf(struct {
|
||||||
|
ATypicalParamName int64 `json:"aTypicalParamName"`
|
||||||
|
}{}), stringKind: "(int64)",
|
||||||
|
TupleElems: []*Type{{Kind: reflect.Int64, T: IntTy, Type: reflect.TypeOf(int64(0)), Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"aTypicalParamName"}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
typ, err := NewType(tt.blob, "", tt.components)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("type %q: failed to parse type string: %v", tt.blob, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(typ, tt.kind) {
|
||||||
|
t.Errorf("type %q: parsed type mismatch:\nGOT %s\nWANT %s ", tt.blob, spew.Sdump(typeWithoutStringer(typ)), spew.Sdump(typeWithoutStringer(tt.kind)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeCheck(t *testing.T) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
typ string
|
||||||
|
components []ArgumentMarshaling
|
||||||
|
input interface{}
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{"uint", nil, big.NewInt(1), "unsupported arg type: uint"},
|
||||||
|
{"int", nil, big.NewInt(1), "unsupported arg type: int"},
|
||||||
|
{"uint256", nil, big.NewInt(1), ""},
|
||||||
|
{"uint256[][3][]", nil, [][3][]*big.Int{{{}}}, ""},
|
||||||
|
{"uint256[][][3]", nil, [3][][]*big.Int{{{}}}, ""},
|
||||||
|
{"uint256[3][][]", nil, [][][3]*big.Int{{{}}}, ""},
|
||||||
|
{"uint256[3][3][3]", nil, [3][3][3]*big.Int{{{}}}, ""},
|
||||||
|
{"uint8[][]", nil, [][]uint8{}, ""},
|
||||||
|
{"int256", nil, big.NewInt(1), ""},
|
||||||
|
{"uint8", nil, uint8(1), ""},
|
||||||
|
{"uint16", nil, uint16(1), ""},
|
||||||
|
{"uint32", nil, uint32(1), ""},
|
||||||
|
{"uint64", nil, uint64(1), ""},
|
||||||
|
{"int8", nil, int8(1), ""},
|
||||||
|
{"int16", nil, int16(1), ""},
|
||||||
|
{"int32", nil, int32(1), ""},
|
||||||
|
{"int64", nil, int64(1), ""},
|
||||||
|
{"uint24", nil, big.NewInt(1), ""},
|
||||||
|
{"uint40", nil, big.NewInt(1), ""},
|
||||||
|
{"uint48", nil, big.NewInt(1), ""},
|
||||||
|
{"uint56", nil, big.NewInt(1), ""},
|
||||||
|
{"uint72", nil, big.NewInt(1), ""},
|
||||||
|
{"uint80", nil, big.NewInt(1), ""},
|
||||||
|
{"uint88", nil, big.NewInt(1), ""},
|
||||||
|
{"uint96", nil, big.NewInt(1), ""},
|
||||||
|
{"uint104", nil, big.NewInt(1), ""},
|
||||||
|
{"uint112", nil, big.NewInt(1), ""},
|
||||||
|
{"uint120", nil, big.NewInt(1), ""},
|
||||||
|
{"uint128", nil, big.NewInt(1), ""},
|
||||||
|
{"uint136", nil, big.NewInt(1), ""},
|
||||||
|
{"uint144", nil, big.NewInt(1), ""},
|
||||||
|
{"uint152", nil, big.NewInt(1), ""},
|
||||||
|
{"uint160", nil, big.NewInt(1), ""},
|
||||||
|
{"uint168", nil, big.NewInt(1), ""},
|
||||||
|
{"uint176", nil, big.NewInt(1), ""},
|
||||||
|
{"uint184", nil, big.NewInt(1), ""},
|
||||||
|
{"uint192", nil, big.NewInt(1), ""},
|
||||||
|
{"uint200", nil, big.NewInt(1), ""},
|
||||||
|
{"uint208", nil, big.NewInt(1), ""},
|
||||||
|
{"uint216", nil, big.NewInt(1), ""},
|
||||||
|
{"uint224", nil, big.NewInt(1), ""},
|
||||||
|
{"uint232", nil, big.NewInt(1), ""},
|
||||||
|
{"uint240", nil, big.NewInt(1), ""},
|
||||||
|
{"uint248", nil, big.NewInt(1), ""},
|
||||||
|
{"int24", nil, big.NewInt(1), ""},
|
||||||
|
{"int40", nil, big.NewInt(1), ""},
|
||||||
|
{"int48", nil, big.NewInt(1), ""},
|
||||||
|
{"int56", nil, big.NewInt(1), ""},
|
||||||
|
{"int72", nil, big.NewInt(1), ""},
|
||||||
|
{"int80", nil, big.NewInt(1), ""},
|
||||||
|
{"int88", nil, big.NewInt(1), ""},
|
||||||
|
{"int96", nil, big.NewInt(1), ""},
|
||||||
|
{"int104", nil, big.NewInt(1), ""},
|
||||||
|
{"int112", nil, big.NewInt(1), ""},
|
||||||
|
{"int120", nil, big.NewInt(1), ""},
|
||||||
|
{"int128", nil, big.NewInt(1), ""},
|
||||||
|
{"int136", nil, big.NewInt(1), ""},
|
||||||
|
{"int144", nil, big.NewInt(1), ""},
|
||||||
|
{"int152", nil, big.NewInt(1), ""},
|
||||||
|
{"int160", nil, big.NewInt(1), ""},
|
||||||
|
{"int168", nil, big.NewInt(1), ""},
|
||||||
|
{"int176", nil, big.NewInt(1), ""},
|
||||||
|
{"int184", nil, big.NewInt(1), ""},
|
||||||
|
{"int192", nil, big.NewInt(1), ""},
|
||||||
|
{"int200", nil, big.NewInt(1), ""},
|
||||||
|
{"int208", nil, big.NewInt(1), ""},
|
||||||
|
{"int216", nil, big.NewInt(1), ""},
|
||||||
|
{"int224", nil, big.NewInt(1), ""},
|
||||||
|
{"int232", nil, big.NewInt(1), ""},
|
||||||
|
{"int240", nil, big.NewInt(1), ""},
|
||||||
|
{"int248", nil, big.NewInt(1), ""},
|
||||||
|
{"uint30", nil, uint8(1), "abi: cannot use uint8 as type ptr as argument"},
|
||||||
|
{"uint8", nil, uint16(1), "abi: cannot use uint16 as type uint8 as argument"},
|
||||||
|
{"uint8", nil, uint32(1), "abi: cannot use uint32 as type uint8 as argument"},
|
||||||
|
{"uint8", nil, uint64(1), "abi: cannot use uint64 as type uint8 as argument"},
|
||||||
|
{"uint8", nil, int8(1), "abi: cannot use int8 as type uint8 as argument"},
|
||||||
|
{"uint8", nil, int16(1), "abi: cannot use int16 as type uint8 as argument"},
|
||||||
|
{"uint8", nil, int32(1), "abi: cannot use int32 as type uint8 as argument"},
|
||||||
|
{"uint8", nil, int64(1), "abi: cannot use int64 as type uint8 as argument"},
|
||||||
|
{"uint16", nil, uint16(1), ""},
|
||||||
|
{"uint16", nil, uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
|
||||||
|
{"uint16[]", nil, []uint16{1, 2, 3}, ""},
|
||||||
|
{"uint16[]", nil, [3]uint16{1, 2, 3}, ""},
|
||||||
|
{"uint16[]", nil, []uint32{1, 2, 3}, "abi: cannot use []uint32 as type [0]uint16 as argument"},
|
||||||
|
{"uint16[3]", nil, [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"},
|
||||||
|
{"uint16[3]", nil, [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||||
|
{"uint16[3]", nil, []uint16{1, 2, 3}, ""},
|
||||||
|
{"uint16[3]", nil, []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
|
||||||
|
{"address[]", nil, []common.Address{{1}}, ""},
|
||||||
|
{"address[1]", nil, []common.Address{{1}}, ""},
|
||||||
|
{"address[1]", nil, [1]common.Address{{1}}, ""},
|
||||||
|
{"address[2]", nil, [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
|
||||||
|
{"bytes32", nil, [32]byte{}, ""},
|
||||||
|
{"bytes31", nil, [31]byte{}, ""},
|
||||||
|
{"bytes30", nil, [30]byte{}, ""},
|
||||||
|
{"bytes29", nil, [29]byte{}, ""},
|
||||||
|
{"bytes28", nil, [28]byte{}, ""},
|
||||||
|
{"bytes27", nil, [27]byte{}, ""},
|
||||||
|
{"bytes26", nil, [26]byte{}, ""},
|
||||||
|
{"bytes25", nil, [25]byte{}, ""},
|
||||||
|
{"bytes24", nil, [24]byte{}, ""},
|
||||||
|
{"bytes23", nil, [23]byte{}, ""},
|
||||||
|
{"bytes22", nil, [22]byte{}, ""},
|
||||||
|
{"bytes21", nil, [21]byte{}, ""},
|
||||||
|
{"bytes20", nil, [20]byte{}, ""},
|
||||||
|
{"bytes19", nil, [19]byte{}, ""},
|
||||||
|
{"bytes18", nil, [18]byte{}, ""},
|
||||||
|
{"bytes17", nil, [17]byte{}, ""},
|
||||||
|
{"bytes16", nil, [16]byte{}, ""},
|
||||||
|
{"bytes15", nil, [15]byte{}, ""},
|
||||||
|
{"bytes14", nil, [14]byte{}, ""},
|
||||||
|
{"bytes13", nil, [13]byte{}, ""},
|
||||||
|
{"bytes12", nil, [12]byte{}, ""},
|
||||||
|
{"bytes11", nil, [11]byte{}, ""},
|
||||||
|
{"bytes10", nil, [10]byte{}, ""},
|
||||||
|
{"bytes9", nil, [9]byte{}, ""},
|
||||||
|
{"bytes8", nil, [8]byte{}, ""},
|
||||||
|
{"bytes7", nil, [7]byte{}, ""},
|
||||||
|
{"bytes6", nil, [6]byte{}, ""},
|
||||||
|
{"bytes5", nil, [5]byte{}, ""},
|
||||||
|
{"bytes4", nil, [4]byte{}, ""},
|
||||||
|
{"bytes3", nil, [3]byte{}, ""},
|
||||||
|
{"bytes2", nil, [2]byte{}, ""},
|
||||||
|
{"bytes1", nil, [1]byte{}, ""},
|
||||||
|
{"bytes32", nil, [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
|
||||||
|
{"bytes32", nil, common.Hash{1}, ""},
|
||||||
|
{"bytes31", nil, common.Hash{1}, "abi: cannot use common.Hash as type [31]uint8 as argument"},
|
||||||
|
{"bytes31", nil, [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
|
||||||
|
{"bytes", nil, []byte{0, 1}, ""},
|
||||||
|
{"bytes", nil, [2]byte{0, 1}, "abi: cannot use array as type slice as argument"},
|
||||||
|
{"bytes", nil, common.Hash{1}, "abi: cannot use array as type slice as argument"},
|
||||||
|
{"string", nil, "hello world", ""},
|
||||||
|
{"string", nil, string(""), ""},
|
||||||
|
{"string", nil, []byte{}, "abi: cannot use slice as type string as argument"},
|
||||||
|
{"bytes32[]", nil, [][32]byte{{}}, ""},
|
||||||
|
{"function", nil, [24]byte{}, ""},
|
||||||
|
{"bytes20", nil, common.Address{}, ""},
|
||||||
|
{"address", nil, [20]byte{}, ""},
|
||||||
|
{"address", nil, common.Address{}, ""},
|
||||||
|
{"bytes32[]]", nil, "", "invalid arg type in abi"},
|
||||||
|
{"invalidType", nil, "", "unsupported arg type: invalidType"},
|
||||||
|
{"invalidSlice[]", nil, "", "unsupported arg type: invalidSlice"},
|
||||||
|
// simple tuple
|
||||||
|
{"tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{}, ""},
|
||||||
|
// tuple slice
|
||||||
|
{"tuple[]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{}, ""},
|
||||||
|
// tuple array
|
||||||
|
{"tuple[2]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct {
|
||||||
|
A *big.Int
|
||||||
|
B *big.Int
|
||||||
|
}{{big.NewInt(0), big.NewInt(0)}, {big.NewInt(0), big.NewInt(0)}}, ""},
|
||||||
|
} {
|
||||||
|
typ, err := NewType(test.typ, "", test.components)
|
||||||
|
if err != nil && len(test.err) == 0 {
|
||||||
|
t.Fatal("unexpected parse error:", err)
|
||||||
|
} else if err != nil && len(test.err) != 0 {
|
||||||
|
if err.Error() != test.err {
|
||||||
|
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = typeCheck(typ, reflect.ValueOf(test.input))
|
||||||
|
if err != nil && len(test.err) == 0 {
|
||||||
|
t.Errorf("%d failed. Expected no err but got: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err == nil && len(test.err) != 0 {
|
||||||
|
t.Errorf("%d failed. Expected err: %v but got none", i, test.err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && len(test.err) != 0 && err.Error() != test.err {
|
||||||
|
t.Errorf("%d failed. Expected err: '%v' got err: '%v'", i, test.err, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1153
accounts/abi/unpack_test.go
Normal file
1153
accounts/abi/unpack_test.go
Normal file
File diff suppressed because it is too large
Load Diff
32
accounts/accounts_test.go
Normal file
32
accounts/accounts_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2015 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 accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTextHash(t *testing.T) {
|
||||||
|
hash := TextHash([]byte("Hello Joe"))
|
||||||
|
want := hexutil.MustDecode("0xa080337ae51c4e064c189e113edd0ba391df9206e2f49db658bb32cf2911730b")
|
||||||
|
if !bytes.Equal(hash, want) {
|
||||||
|
t.Fatalf("wrong hash: %x", hash)
|
||||||
|
}
|
||||||
|
}
|
68
accounts/errors.go
Normal file
68
accounts/errors.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2017 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 accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrUnknownAccount is returned for any requested operation for which no backend
|
||||||
|
// provides the specified account.
|
||||||
|
var ErrUnknownAccount = errors.New("unknown account")
|
||||||
|
|
||||||
|
// ErrUnknownWallet is returned for any requested operation for which no backend
|
||||||
|
// provides the specified wallet.
|
||||||
|
var ErrUnknownWallet = errors.New("unknown wallet")
|
||||||
|
|
||||||
|
// ErrNotSupported is returned when an operation is requested from an account
|
||||||
|
// backend that it does not support.
|
||||||
|
var ErrNotSupported = errors.New("not supported")
|
||||||
|
|
||||||
|
// ErrInvalidPassphrase is returned when a decryption operation receives a bad
|
||||||
|
// passphrase.
|
||||||
|
var ErrInvalidPassphrase = errors.New("invalid password")
|
||||||
|
|
||||||
|
// ErrWalletAlreadyOpen is returned if a wallet is attempted to be opened the
|
||||||
|
// second time.
|
||||||
|
var ErrWalletAlreadyOpen = errors.New("wallet already open")
|
||||||
|
|
||||||
|
// ErrWalletClosed is returned if a wallet is attempted to be opened the
|
||||||
|
// secodn time.
|
||||||
|
var ErrWalletClosed = errors.New("wallet closed")
|
||||||
|
|
||||||
|
// AuthNeededError is returned by backends for signing requests where the user
|
||||||
|
// is required to provide further authentication before signing can succeed.
|
||||||
|
//
|
||||||
|
// This usually means either that a password needs to be supplied, or perhaps a
|
||||||
|
// one time PIN code displayed by some hardware device.
|
||||||
|
type AuthNeededError struct {
|
||||||
|
Needed string // Extra authentication the user needs to provide
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAuthNeededError creates a new authentication error with the extra details
|
||||||
|
// about the needed fields set.
|
||||||
|
func NewAuthNeededError(needed string) error {
|
||||||
|
return &AuthNeededError{
|
||||||
|
Needed: needed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the standard error interface.
|
||||||
|
func (err *AuthNeededError) Error() string {
|
||||||
|
return fmt.Sprintf("authentication needed: %s", err.Needed)
|
||||||
|
}
|
231
accounts/external/backend.go
vendored
Normal file
231
accounts/external/backend.go
vendored
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
// 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
|
||||||
|
// 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 external
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
"github.com/ethereum/go-ethereum/signer/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExternalBackend struct {
|
||||||
|
signers []accounts.Wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eb *ExternalBackend) Wallets() []accounts.Wallet {
|
||||||
|
return eb.signers
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExternalBackend(endpoint string) (*ExternalBackend, error) {
|
||||||
|
signer, err := NewExternalSigner(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ExternalBackend{
|
||||||
|
signers: []accounts.Wallet{signer},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eb *ExternalBackend) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
|
||||||
|
return event.NewSubscription(func(quit <-chan struct{}) error {
|
||||||
|
<-quit
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalSigner provides an API to interact with an external signer (clef)
|
||||||
|
// It proxies request to the external signer while forwarding relevant
|
||||||
|
// request headers
|
||||||
|
type ExternalSigner struct {
|
||||||
|
client *rpc.Client
|
||||||
|
endpoint string
|
||||||
|
status string
|
||||||
|
cacheMu sync.RWMutex
|
||||||
|
cache []accounts.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExternalSigner(endpoint string) (*ExternalSigner, error) {
|
||||||
|
client, err := rpc.Dial(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extsigner := &ExternalSigner{
|
||||||
|
client: client,
|
||||||
|
endpoint: endpoint,
|
||||||
|
}
|
||||||
|
// Check if reachable
|
||||||
|
version, err := extsigner.pingVersion()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extsigner.status = fmt.Sprintf("ok [version=%v]", version)
|
||||||
|
return extsigner, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) URL() accounts.URL {
|
||||||
|
return accounts.URL{
|
||||||
|
Scheme: "extapi",
|
||||||
|
Path: api.endpoint,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Status() (string, error) {
|
||||||
|
return api.status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Open(passphrase string) error {
|
||||||
|
return fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Close() error {
|
||||||
|
return fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Accounts() []accounts.Account {
|
||||||
|
var accnts []accounts.Account
|
||||||
|
res, err := api.listAccounts()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("account listing failed", "error", err)
|
||||||
|
return accnts
|
||||||
|
}
|
||||||
|
for _, addr := range res {
|
||||||
|
accnts = append(accnts, accounts.Account{
|
||||||
|
URL: accounts.URL{
|
||||||
|
Scheme: "extapi",
|
||||||
|
Path: api.endpoint,
|
||||||
|
},
|
||||||
|
Address: addr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
api.cacheMu.Lock()
|
||||||
|
api.cache = accnts
|
||||||
|
api.cacheMu.Unlock()
|
||||||
|
return accnts
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Contains(account accounts.Account) bool {
|
||||||
|
api.cacheMu.RLock()
|
||||||
|
defer api.cacheMu.RUnlock()
|
||||||
|
for _, a := range api.cache {
|
||||||
|
if a.Address == account.Address && (account.URL == (accounts.URL{}) || account.URL == api.URL()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
||||||
|
return accounts.Account{}, fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
||||||
|
log.Error("operation SelfDerive not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) signHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||||
|
return []byte{}, fmt.Errorf("operation not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
|
||||||
|
func (api *ExternalSigner) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
var res hexutil.Bytes
|
||||||
|
var signAddress = common.NewMixedcaseAddress(account.Address)
|
||||||
|
if err := api.client.Call(&res, "account_signData",
|
||||||
|
mimeType,
|
||||||
|
&signAddress, // Need to use the pointer here, because of how MarshalJSON is defined
|
||||||
|
hexutil.Encode(data)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// If V is on 27/28-form, convert to to 0/1 for Clique
|
||||||
|
if mimeType == accounts.MimetypeClique && (res[64] == 27 || res[64] == 28) {
|
||||||
|
res[64] -= 27 // Transform V from 27/28 to 0/1 for Clique use
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignText(account accounts.Account, text []byte) ([]byte, error) {
|
||||||
|
var res hexutil.Bytes
|
||||||
|
var signAddress = common.NewMixedcaseAddress(account.Address)
|
||||||
|
if err := api.client.Call(&res, "account_signData",
|
||||||
|
accounts.MimetypeTextPlain,
|
||||||
|
&signAddress, // Need to use the pointer here, because of how MarshalJSON is defined
|
||||||
|
hexutil.Encode(text)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
|
res := ethapi.SignTransactionResult{}
|
||||||
|
data := hexutil.Bytes(tx.Data())
|
||||||
|
var to *common.MixedcaseAddress
|
||||||
|
if tx.To() != nil {
|
||||||
|
t := common.NewMixedcaseAddress(*tx.To())
|
||||||
|
to = &t
|
||||||
|
}
|
||||||
|
args := &core.SendTxArgs{
|
||||||
|
Data: &data,
|
||||||
|
Nonce: hexutil.Uint64(tx.Nonce()),
|
||||||
|
Value: hexutil.Big(*tx.Value()),
|
||||||
|
Gas: hexutil.Uint64(tx.Gas()),
|
||||||
|
GasPrice: hexutil.Big(*tx.GasPrice()),
|
||||||
|
To: to,
|
||||||
|
From: common.NewMixedcaseAddress(account.Address),
|
||||||
|
}
|
||||||
|
if err := api.client.Call(&res, "account_signTransaction", args); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res.Tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
|
||||||
|
return []byte{}, fmt.Errorf("password-operations not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
|
return nil, fmt.Errorf("password-operations not supported on external signers")
|
||||||
|
}
|
||||||
|
func (api *ExternalSigner) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
|
||||||
|
return nil, fmt.Errorf("password-operations not supported on external signers")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) listAccounts() ([]common.Address, error) {
|
||||||
|
var res []common.Address
|
||||||
|
if err := api.client.Call(&res, "account_list"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *ExternalSigner) pingVersion() (string, error) {
|
||||||
|
var v string
|
||||||
|
if err := api.client.Call(&v, "account_version"); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
79
accounts/hd_test.go
Normal file
79
accounts/hd_test.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Copyright 2017 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 accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that HD derivation paths can be correctly parsed into our internal binary
|
||||||
|
// representation.
|
||||||
|
func TestHDPathParsing(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
output DerivationPath
|
||||||
|
}{
|
||||||
|
// Plain absolute derivation paths
|
||||||
|
{"m/44'/60'/0'/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||||
|
{"m/44'/60'/0'/128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}},
|
||||||
|
{"m/44'/60'/0'/0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
||||||
|
{"m/44'/60'/0'/128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}},
|
||||||
|
{"m/2147483692/2147483708/2147483648/0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||||
|
{"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
||||||
|
|
||||||
|
// Plain relative derivation paths
|
||||||
|
{"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}},
|
||||||
|
{"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}},
|
||||||
|
{"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||||
|
{"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}},
|
||||||
|
{"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||||
|
|
||||||
|
// Hexadecimal absolute derivation paths
|
||||||
|
{"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||||
|
{"m/0x2C'/0x3c'/0x00'/0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}},
|
||||||
|
{"m/0x2C'/0x3c'/0x00'/0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
||||||
|
{"m/0x2C'/0x3c'/0x00'/0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}},
|
||||||
|
{"m/0x8000002C/0x8000003c/0x80000000/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||||
|
{"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
||||||
|
|
||||||
|
// Hexadecimal relative derivation paths
|
||||||
|
{"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}},
|
||||||
|
{"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}},
|
||||||
|
{"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||||
|
{"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}},
|
||||||
|
{"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||||
|
|
||||||
|
// Weird inputs just to ensure they work
|
||||||
|
{" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||||
|
|
||||||
|
// Invaid derivation paths
|
||||||
|
{"", nil}, // Empty relative derivation path
|
||||||
|
{"m", nil}, // Empty absolute derivation path
|
||||||
|
{"m/", nil}, // Missing last derivation component
|
||||||
|
{"/44'/60'/0'/0", nil}, // Absolute path without m prefix, might be user error
|
||||||
|
{"m/2147483648'", nil}, // Overflows 32 bit integer
|
||||||
|
{"m/-1'", nil}, // Cannot contain negative number
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
if path, err := ParseDerivationPath(tt.input); !reflect.DeepEqual(path, tt.output) {
|
||||||
|
t.Errorf("test %d: parse mismatch: have %v (%v), want %v", i, path, err, tt.output)
|
||||||
|
} else if path == nil && err == nil {
|
||||||
|
t.Errorf("test %d: nil path and error: %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
406
accounts/keystore/account_cache_test.go
Normal file
406
accounts/keystore/account_cache_test.go
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
// Copyright 2017 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 keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/cespare/cp"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cachetestDir, _ = filepath.Abs(filepath.Join("testdata", "keystore"))
|
||||||
|
cachetestAccounts = []accounts.Account{
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "aaa")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(cachetestDir, "zzz")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWatchNewFile(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir, ks := tmpKeyStore(t, false)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
// Ensure the watcher is started before adding any files.
|
||||||
|
ks.Accounts()
|
||||||
|
time.Sleep(1000 * time.Millisecond)
|
||||||
|
|
||||||
|
// Move in the files.
|
||||||
|
wantAccounts := make([]accounts.Account, len(cachetestAccounts))
|
||||||
|
for i := range cachetestAccounts {
|
||||||
|
wantAccounts[i] = accounts.Account{
|
||||||
|
Address: cachetestAccounts[i].Address,
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, filepath.Base(cachetestAccounts[i].URL.Path))},
|
||||||
|
}
|
||||||
|
if err := cp.CopyFile(wantAccounts[i].URL.Path, cachetestAccounts[i].URL.Path); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ks should see the accounts.
|
||||||
|
var list []accounts.Account
|
||||||
|
for d := 200 * time.Millisecond; d < 5*time.Second; d *= 2 {
|
||||||
|
list = ks.Accounts()
|
||||||
|
if reflect.DeepEqual(list, wantAccounts) {
|
||||||
|
// ks should have also received change notifications
|
||||||
|
select {
|
||||||
|
case <-ks.changes:
|
||||||
|
default:
|
||||||
|
t.Fatalf("wasn't notified of new accounts")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
||||||
|
t.Errorf("got %s, want %s", spew.Sdump(list), spew.Sdump(wantAccounts))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWatchNoDir(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Create ks but not the directory that it watches.
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
|
||||||
|
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
||||||
|
|
||||||
|
list := ks.Accounts()
|
||||||
|
if len(list) > 0 {
|
||||||
|
t.Error("initial account list not empty:", list)
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Create the directory and copy a key file into it.
|
||||||
|
os.MkdirAll(dir, 0700)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
file := filepath.Join(dir, "aaa")
|
||||||
|
if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ks should see the account.
|
||||||
|
wantAccounts := []accounts.Account{cachetestAccounts[0]}
|
||||||
|
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
|
||||||
|
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
|
||||||
|
list = ks.Accounts()
|
||||||
|
if reflect.DeepEqual(list, wantAccounts) {
|
||||||
|
// ks should have also received change notifications
|
||||||
|
select {
|
||||||
|
case <-ks.changes:
|
||||||
|
default:
|
||||||
|
t.Fatalf("wasn't notified of new accounts")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
||||||
|
t.Errorf("\ngot %v\nwant %v", list, wantAccounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheInitialReload(t *testing.T) {
|
||||||
|
cache, _ := newAccountCache(cachetestDir)
|
||||||
|
accounts := cache.accounts()
|
||||||
|
if !reflect.DeepEqual(accounts, cachetestAccounts) {
|
||||||
|
t.Fatalf("got initial accounts: %swant %s", spew.Sdump(accounts), spew.Sdump(cachetestAccounts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheAddDeleteOrder(t *testing.T) {
|
||||||
|
cache, _ := newAccountCache("testdata/no-such-dir")
|
||||||
|
cache.watcher.running = true // prevent unexpected reloads
|
||||||
|
|
||||||
|
accs := []accounts.Account{
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "-309830980"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "ggg"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("8bda78331c916a08481428e4b07c96d3e916d165"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzzzzz-the-very-last-one.keyXXX"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "SOMETHING.key"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "aaa"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("289d485d9771714cce91d3393d764e1311907acc"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: "zzz"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, a := range accs {
|
||||||
|
cache.add(a)
|
||||||
|
}
|
||||||
|
// Add some of them twice to check that they don't get reinserted.
|
||||||
|
cache.add(accs[0])
|
||||||
|
cache.add(accs[2])
|
||||||
|
|
||||||
|
// Check that the account list is sorted by filename.
|
||||||
|
wantAccounts := make([]accounts.Account, len(accs))
|
||||||
|
copy(wantAccounts, accs)
|
||||||
|
sort.Sort(accountsByURL(wantAccounts))
|
||||||
|
list := cache.accounts()
|
||||||
|
if !reflect.DeepEqual(list, wantAccounts) {
|
||||||
|
t.Fatalf("got accounts: %s\nwant %s", spew.Sdump(accs), spew.Sdump(wantAccounts))
|
||||||
|
}
|
||||||
|
for _, a := range accs {
|
||||||
|
if !cache.hasAddress(a.Address) {
|
||||||
|
t.Errorf("expected hasAccount(%x) to return true", a.Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cache.hasAddress(common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e")) {
|
||||||
|
t.Errorf("expected hasAccount(%x) to return false", common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a few keys from the cache.
|
||||||
|
for i := 0; i < len(accs); i += 2 {
|
||||||
|
cache.delete(wantAccounts[i])
|
||||||
|
}
|
||||||
|
cache.delete(accounts.Account{Address: common.HexToAddress("fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e"), URL: accounts.URL{Scheme: KeyStoreScheme, Path: "something"}})
|
||||||
|
|
||||||
|
// Check content again after deletion.
|
||||||
|
wantAccountsAfterDelete := []accounts.Account{
|
||||||
|
wantAccounts[1],
|
||||||
|
wantAccounts[3],
|
||||||
|
wantAccounts[5],
|
||||||
|
}
|
||||||
|
list = cache.accounts()
|
||||||
|
if !reflect.DeepEqual(list, wantAccountsAfterDelete) {
|
||||||
|
t.Fatalf("got accounts after delete: %s\nwant %s", spew.Sdump(list), spew.Sdump(wantAccountsAfterDelete))
|
||||||
|
}
|
||||||
|
for _, a := range wantAccountsAfterDelete {
|
||||||
|
if !cache.hasAddress(a.Address) {
|
||||||
|
t.Errorf("expected hasAccount(%x) to return true", a.Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cache.hasAddress(wantAccounts[0].Address) {
|
||||||
|
t.Errorf("expected hasAccount(%x) to return false", wantAccounts[0].Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCacheFind(t *testing.T) {
|
||||||
|
dir := filepath.Join("testdata", "dir")
|
||||||
|
cache, _ := newAccountCache(dir)
|
||||||
|
cache.watcher.running = true // prevent unexpected reloads
|
||||||
|
|
||||||
|
accs := []accounts.Account{
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "a.key")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("2cac1adea150210703ba75ed097ddfe24e14f213"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "b.key")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c.key")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: common.HexToAddress("d49ff4eeb0b2686ed89c0fc0f2b6ea533ddbbd5e"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "c2.key")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, a := range accs {
|
||||||
|
cache.add(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
nomatchAccount := accounts.Account{
|
||||||
|
Address: common.HexToAddress("f466859ead1932d743d622cb74fc058882e8648a"),
|
||||||
|
URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Join(dir, "something")},
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
Query accounts.Account
|
||||||
|
WantResult accounts.Account
|
||||||
|
WantError error
|
||||||
|
}{
|
||||||
|
// by address
|
||||||
|
{Query: accounts.Account{Address: accs[0].Address}, WantResult: accs[0]},
|
||||||
|
// by file
|
||||||
|
{Query: accounts.Account{URL: accs[0].URL}, WantResult: accs[0]},
|
||||||
|
// by basename
|
||||||
|
{Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(accs[0].URL.Path)}}, WantResult: accs[0]},
|
||||||
|
// by file and address
|
||||||
|
{Query: accs[0], WantResult: accs[0]},
|
||||||
|
// ambiguous address, tie resolved by file
|
||||||
|
{Query: accs[2], WantResult: accs[2]},
|
||||||
|
// ambiguous address error
|
||||||
|
{
|
||||||
|
Query: accounts.Account{Address: accs[2].Address},
|
||||||
|
WantError: &AmbiguousAddrError{
|
||||||
|
Addr: accs[2].Address,
|
||||||
|
Matches: []accounts.Account{accs[2], accs[3]},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// no match error
|
||||||
|
{Query: nomatchAccount, WantError: ErrNoMatch},
|
||||||
|
{Query: accounts.Account{URL: nomatchAccount.URL}, WantError: ErrNoMatch},
|
||||||
|
{Query: accounts.Account{URL: accounts.URL{Scheme: KeyStoreScheme, Path: filepath.Base(nomatchAccount.URL.Path)}}, WantError: ErrNoMatch},
|
||||||
|
{Query: accounts.Account{Address: nomatchAccount.Address}, WantError: ErrNoMatch},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
a, err := cache.find(test.Query)
|
||||||
|
if !reflect.DeepEqual(err, test.WantError) {
|
||||||
|
t.Errorf("test %d: error mismatch for query %v\ngot %q\nwant %q", i, test.Query, err, test.WantError)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if a != test.WantResult {
|
||||||
|
t.Errorf("test %d: result mismatch for query %v\ngot %v\nwant %v", i, test.Query, a, test.WantResult)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitForAccounts(wantAccounts []accounts.Account, ks *KeyStore) error {
|
||||||
|
var list []accounts.Account
|
||||||
|
for d := 200 * time.Millisecond; d < 8*time.Second; d *= 2 {
|
||||||
|
list = ks.Accounts()
|
||||||
|
if reflect.DeepEqual(list, wantAccounts) {
|
||||||
|
// ks should have also received change notifications
|
||||||
|
select {
|
||||||
|
case <-ks.changes:
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("wasn't notified of new accounts")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
time.Sleep(d)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("\ngot %v\nwant %v", list, wantAccounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestUpdatedKeyfileContents tests that updating the contents of a keystore file
|
||||||
|
// is noticed by the watcher, and the account cache is updated accordingly
|
||||||
|
func TestUpdatedKeyfileContents(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Create a temporary kesytore to test with
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
dir := filepath.Join(os.TempDir(), fmt.Sprintf("eth-keystore-watch-test-%d-%d", os.Getpid(), rand.Int()))
|
||||||
|
ks := NewKeyStore(dir, LightScryptN, LightScryptP)
|
||||||
|
|
||||||
|
list := ks.Accounts()
|
||||||
|
if len(list) > 0 {
|
||||||
|
t.Error("initial account list not empty:", list)
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Create the directory and copy a key file into it.
|
||||||
|
os.MkdirAll(dir, 0700)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
file := filepath.Join(dir, "aaa")
|
||||||
|
|
||||||
|
// Place one of our testfiles in there
|
||||||
|
if err := cp.CopyFile(file, cachetestAccounts[0].URL.Path); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ks should see the account.
|
||||||
|
wantAccounts := []accounts.Account{cachetestAccounts[0]}
|
||||||
|
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
|
||||||
|
if err := waitForAccounts(wantAccounts, ks); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||||
|
time.Sleep(1000 * time.Millisecond)
|
||||||
|
|
||||||
|
// Now replace file contents
|
||||||
|
if err := forceCopyFile(file, cachetestAccounts[1].URL.Path); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wantAccounts = []accounts.Account{cachetestAccounts[1]}
|
||||||
|
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
|
||||||
|
if err := waitForAccounts(wantAccounts, ks); err != nil {
|
||||||
|
t.Errorf("First replacement failed")
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// needed so that modTime of `file` is different to its current value after forceCopyFile
|
||||||
|
time.Sleep(1000 * time.Millisecond)
|
||||||
|
|
||||||
|
// Now replace file contents again
|
||||||
|
if err := forceCopyFile(file, cachetestAccounts[2].URL.Path); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
wantAccounts = []accounts.Account{cachetestAccounts[2]}
|
||||||
|
wantAccounts[0].URL = accounts.URL{Scheme: KeyStoreScheme, Path: file}
|
||||||
|
if err := waitForAccounts(wantAccounts, ks); err != nil {
|
||||||
|
t.Errorf("Second replacement failed")
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// needed so that modTime of `file` is different to its current value after ioutil.WriteFile
|
||||||
|
time.Sleep(1000 * time.Millisecond)
|
||||||
|
|
||||||
|
// Now replace file contents with crap
|
||||||
|
if err := ioutil.WriteFile(file, []byte("foo"), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := waitForAccounts([]accounts.Account{}, ks); err != nil {
|
||||||
|
t.Errorf("Emptying account file failed")
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// forceCopyFile is like cp.CopyFile, but doesn't complain if the destination exists.
|
||||||
|
func forceCopyFile(dst, src string) error {
|
||||||
|
data, err := ioutil.ReadFile(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(dst, data, 0644)
|
||||||
|
}
|
495
accounts/keystore/keystore.go
Normal file
495
accounts/keystore/keystore.go
Normal file
@ -0,0 +1,495 @@
|
|||||||
|
// Copyright 2017 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 keystore implements encrypted storage of secp256k1 private keys.
|
||||||
|
//
|
||||||
|
// Keys are stored as encrypted JSON files according to the Web3 Secret Storage specification.
|
||||||
|
// See https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition for more information.
|
||||||
|
package keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
crand "crypto/rand"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrLocked = accounts.NewAuthNeededError("password or unlock")
|
||||||
|
ErrNoMatch = errors.New("no key for given address or file")
|
||||||
|
ErrDecrypt = errors.New("could not decrypt key with given password")
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyStoreType is the reflect type of a keystore backend.
|
||||||
|
var KeyStoreType = reflect.TypeOf(&KeyStore{})
|
||||||
|
|
||||||
|
// KeyStoreScheme is the protocol scheme prefixing account and wallet URLs.
|
||||||
|
const KeyStoreScheme = "keystore"
|
||||||
|
|
||||||
|
// Maximum time between wallet refreshes (if filesystem notifications don't work).
|
||||||
|
const walletRefreshCycle = 3 * time.Second
|
||||||
|
|
||||||
|
// KeyStore manages a key storage directory on disk.
|
||||||
|
type KeyStore struct {
|
||||||
|
storage keyStore // Storage backend, might be cleartext or encrypted
|
||||||
|
cache *accountCache // In-memory account cache over the filesystem storage
|
||||||
|
changes chan struct{} // Channel receiving change notifications from the cache
|
||||||
|
unlocked map[common.Address]*unlocked // Currently unlocked account (decrypted private keys)
|
||||||
|
|
||||||
|
wallets []accounts.Wallet // Wallet wrappers around the individual key files
|
||||||
|
updateFeed event.Feed // Event feed to notify wallet additions/removals
|
||||||
|
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
|
||||||
|
updating bool // Whether the event notification loop is running
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type unlocked struct {
|
||||||
|
*Key
|
||||||
|
abort chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKeyStore creates a keystore for the given directory.
|
||||||
|
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
|
||||||
|
keydir, _ = filepath.Abs(keydir)
|
||||||
|
ks := &KeyStore{storage: &keyStorePassphrase{keydir, scryptN, scryptP, false}}
|
||||||
|
ks.init(keydir)
|
||||||
|
return ks
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPlaintextKeyStore creates a keystore for the given directory.
|
||||||
|
// Deprecated: Use NewKeyStore.
|
||||||
|
func NewPlaintextKeyStore(keydir string) *KeyStore {
|
||||||
|
keydir, _ = filepath.Abs(keydir)
|
||||||
|
ks := &KeyStore{storage: &keyStorePlain{keydir}}
|
||||||
|
ks.init(keydir)
|
||||||
|
return ks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks *KeyStore) init(keydir string) {
|
||||||
|
// Lock the mutex since the account cache might call back with events
|
||||||
|
ks.mu.Lock()
|
||||||
|
defer ks.mu.Unlock()
|
||||||
|
|
||||||
|
// Initialize the set of unlocked keys and the account cache
|
||||||
|
ks.unlocked = make(map[common.Address]*unlocked)
|
||||||
|
ks.cache, ks.changes = newAccountCache(keydir)
|
||||||
|
|
||||||
|
// TODO: In order for this finalizer to work, there must be no references
|
||||||
|
// to ks. addressCache doesn't keep a reference but unlocked keys do,
|
||||||
|
// so the finalizer will not trigger until all timed unlocks have expired.
|
||||||
|
runtime.SetFinalizer(ks, func(m *KeyStore) {
|
||||||
|
m.cache.close()
|
||||||
|
})
|
||||||
|
// Create the initial list of wallets from the cache
|
||||||
|
accs := ks.cache.accounts()
|
||||||
|
ks.wallets = make([]accounts.Wallet, len(accs))
|
||||||
|
for i := 0; i < len(accs); i++ {
|
||||||
|
ks.wallets[i] = &keystoreWallet{account: accs[i], keystore: ks}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wallets implements accounts.Backend, returning all single-key wallets from the
|
||||||
|
// keystore directory.
|
||||||
|
func (ks *KeyStore) Wallets() []accounts.Wallet {
|
||||||
|
// Make sure the list of wallets is in sync with the account cache
|
||||||
|
ks.refreshWallets()
|
||||||
|
|
||||||
|
ks.mu.RLock()
|
||||||
|
defer ks.mu.RUnlock()
|
||||||
|
|
||||||
|
cpy := make([]accounts.Wallet, len(ks.wallets))
|
||||||
|
copy(cpy, ks.wallets)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// refreshWallets retrieves the current account list and based on that does any
|
||||||
|
// necessary wallet refreshes.
|
||||||
|
func (ks *KeyStore) refreshWallets() {
|
||||||
|
// Retrieve the current list of accounts
|
||||||
|
ks.mu.Lock()
|
||||||
|
accs := ks.cache.accounts()
|
||||||
|
|
||||||
|
// Transform the current list of wallets into the new one
|
||||||
|
var (
|
||||||
|
wallets = make([]accounts.Wallet, 0, len(accs))
|
||||||
|
events []accounts.WalletEvent
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, account := range accs {
|
||||||
|
// Drop wallets while they were in front of the next account
|
||||||
|
for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 {
|
||||||
|
events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped})
|
||||||
|
ks.wallets = ks.wallets[1:]
|
||||||
|
}
|
||||||
|
// If there are no more wallets or the account is before the next, wrap new wallet
|
||||||
|
if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 {
|
||||||
|
wallet := &keystoreWallet{account: account, keystore: ks}
|
||||||
|
|
||||||
|
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
|
||||||
|
wallets = append(wallets, wallet)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If the account is the same as the first wallet, keep it
|
||||||
|
if ks.wallets[0].Accounts()[0] == account {
|
||||||
|
wallets = append(wallets, ks.wallets[0])
|
||||||
|
ks.wallets = ks.wallets[1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Drop any leftover wallets and set the new batch
|
||||||
|
for _, wallet := range ks.wallets {
|
||||||
|
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
|
||||||
|
}
|
||||||
|
ks.wallets = wallets
|
||||||
|
ks.mu.Unlock()
|
||||||
|
|
||||||
|
// Fire all wallet events and return
|
||||||
|
for _, event := range events {
|
||||||
|
ks.updateFeed.Send(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe implements accounts.Backend, creating an async subscription to
|
||||||
|
// receive notifications on the addition or removal of keystore wallets.
|
||||||
|
func (ks *KeyStore) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
|
||||||
|
// We need the mutex to reliably start/stop the update loop
|
||||||
|
ks.mu.Lock()
|
||||||
|
defer ks.mu.Unlock()
|
||||||
|
|
||||||
|
// Subscribe the caller and track the subscriber count
|
||||||
|
sub := ks.updateScope.Track(ks.updateFeed.Subscribe(sink))
|
||||||
|
|
||||||
|
// Subscribers require an active notification loop, start it
|
||||||
|
if !ks.updating {
|
||||||
|
ks.updating = true
|
||||||
|
go ks.updater()
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
// updater is responsible for maintaining an up-to-date list of wallets stored in
|
||||||
|
// the keystore, and for firing wallet addition/removal events. It listens for
|
||||||
|
// account change events from the underlying account cache, and also periodically
|
||||||
|
// forces a manual refresh (only triggers for systems where the filesystem notifier
|
||||||
|
// is not running).
|
||||||
|
func (ks *KeyStore) updater() {
|
||||||
|
for {
|
||||||
|
// Wait for an account update or a refresh timeout
|
||||||
|
select {
|
||||||
|
case <-ks.changes:
|
||||||
|
case <-time.After(walletRefreshCycle):
|
||||||
|
}
|
||||||
|
// Run the wallet refresher
|
||||||
|
ks.refreshWallets()
|
||||||
|
|
||||||
|
// If all our subscribers left, stop the updater
|
||||||
|
ks.mu.Lock()
|
||||||
|
if ks.updateScope.Count() == 0 {
|
||||||
|
ks.updating = false
|
||||||
|
ks.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ks.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasAddress reports whether a key with the given address is present.
|
||||||
|
func (ks *KeyStore) HasAddress(addr common.Address) bool {
|
||||||
|
return ks.cache.hasAddress(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accounts returns all key files present in the directory.
|
||||||
|
func (ks *KeyStore) Accounts() []accounts.Account {
|
||||||
|
return ks.cache.accounts()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete deletes the key matched by account if the passphrase is correct.
|
||||||
|
// If the account contains no filename, the address must match a unique key.
|
||||||
|
func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error {
|
||||||
|
// Decrypting the key isn't really necessary, but we do
|
||||||
|
// it anyway to check the password and zero out the key
|
||||||
|
// immediately afterwards.
|
||||||
|
a, key, err := ks.getDecryptedKey(a, passphrase)
|
||||||
|
if key != nil {
|
||||||
|
zeroKey(key.PrivateKey)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// The order is crucial here. The key is dropped from the
|
||||||
|
// cache after the file is gone so that a reload happening in
|
||||||
|
// between won't insert it into the cache again.
|
||||||
|
err = os.Remove(a.URL.Path)
|
||||||
|
if err == nil {
|
||||||
|
ks.cache.delete(a)
|
||||||
|
ks.refreshWallets()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignHash calculates a ECDSA signature for the given hash. The produced
|
||||||
|
// signature is in the [R || S || V] format where V is 0 or 1.
|
||||||
|
func (ks *KeyStore) SignHash(a accounts.Account, hash []byte) ([]byte, error) {
|
||||||
|
// Look up the key to sign with and abort if it cannot be found
|
||||||
|
ks.mu.RLock()
|
||||||
|
defer ks.mu.RUnlock()
|
||||||
|
|
||||||
|
unlockedKey, found := ks.unlocked[a.Address]
|
||||||
|
if !found {
|
||||||
|
return nil, ErrLocked
|
||||||
|
}
|
||||||
|
// Sign the hash using plain ECDSA operations
|
||||||
|
return crypto.Sign(hash, unlockedKey.PrivateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTx signs the given transaction with the requested account.
|
||||||
|
func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
|
// Look up the key to sign with and abort if it cannot be found
|
||||||
|
ks.mu.RLock()
|
||||||
|
defer ks.mu.RUnlock()
|
||||||
|
|
||||||
|
unlockedKey, found := ks.unlocked[a.Address]
|
||||||
|
if !found {
|
||||||
|
return nil, ErrLocked
|
||||||
|
}
|
||||||
|
// Depending on the presence of the chain ID, sign with EIP155 or homestead
|
||||||
|
if chainID != nil {
|
||||||
|
return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey)
|
||||||
|
}
|
||||||
|
return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignHashWithPassphrase signs hash if the private key matching the given address
|
||||||
|
// can be decrypted with the given passphrase. The produced signature is in the
|
||||||
|
// [R || S || V] format where V is 0 or 1.
|
||||||
|
func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) {
|
||||||
|
_, key, err := ks.getDecryptedKey(a, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer zeroKey(key.PrivateKey)
|
||||||
|
return crypto.Sign(hash, key.PrivateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTxWithPassphrase signs the transaction if the private key matching the
|
||||||
|
// given address can be decrypted with the given passphrase.
|
||||||
|
func (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
|
_, key, err := ks.getDecryptedKey(a, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer zeroKey(key.PrivateKey)
|
||||||
|
|
||||||
|
// Depending on the presence of the chain ID, sign with EIP155 or homestead
|
||||||
|
if chainID != nil {
|
||||||
|
return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey)
|
||||||
|
}
|
||||||
|
return types.SignTx(tx, types.HomesteadSigner{}, key.PrivateKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock unlocks the given account indefinitely.
|
||||||
|
func (ks *KeyStore) Unlock(a accounts.Account, passphrase string) error {
|
||||||
|
return ks.TimedUnlock(a, passphrase, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock removes the private key with the given address from memory.
|
||||||
|
func (ks *KeyStore) Lock(addr common.Address) error {
|
||||||
|
ks.mu.Lock()
|
||||||
|
if unl, found := ks.unlocked[addr]; found {
|
||||||
|
ks.mu.Unlock()
|
||||||
|
ks.expire(addr, unl, time.Duration(0)*time.Nanosecond)
|
||||||
|
} else {
|
||||||
|
ks.mu.Unlock()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimedUnlock unlocks the given account with the passphrase. The account
|
||||||
|
// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account
|
||||||
|
// until the program exits. The account must match a unique key file.
|
||||||
|
//
|
||||||
|
// If the account address is already unlocked for a duration, TimedUnlock extends or
|
||||||
|
// shortens the active unlock timeout. If the address was previously unlocked
|
||||||
|
// indefinitely the timeout is not altered.
|
||||||
|
func (ks *KeyStore) TimedUnlock(a accounts.Account, passphrase string, timeout time.Duration) error {
|
||||||
|
a, key, err := ks.getDecryptedKey(a, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ks.mu.Lock()
|
||||||
|
defer ks.mu.Unlock()
|
||||||
|
u, found := ks.unlocked[a.Address]
|
||||||
|
if found {
|
||||||
|
if u.abort == nil {
|
||||||
|
// The address was unlocked indefinitely, so unlocking
|
||||||
|
// it with a timeout would be confusing.
|
||||||
|
zeroKey(key.PrivateKey)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Terminate the expire goroutine and replace it below.
|
||||||
|
close(u.abort)
|
||||||
|
}
|
||||||
|
if timeout > 0 {
|
||||||
|
u = &unlocked{Key: key, abort: make(chan struct{})}
|
||||||
|
go ks.expire(a.Address, u, timeout)
|
||||||
|
} else {
|
||||||
|
u = &unlocked{Key: key}
|
||||||
|
}
|
||||||
|
ks.unlocked[a.Address] = u
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find resolves the given account into a unique entry in the keystore.
|
||||||
|
func (ks *KeyStore) Find(a accounts.Account) (accounts.Account, error) {
|
||||||
|
ks.cache.maybeReload()
|
||||||
|
ks.cache.mu.Lock()
|
||||||
|
a, err := ks.cache.find(a)
|
||||||
|
ks.cache.mu.Unlock()
|
||||||
|
return a, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks *KeyStore) getDecryptedKey(a accounts.Account, auth string) (accounts.Account, *Key, error) {
|
||||||
|
a, err := ks.Find(a)
|
||||||
|
if err != nil {
|
||||||
|
return a, nil, err
|
||||||
|
}
|
||||||
|
key, err := ks.storage.GetKey(a.Address, a.URL.Path, auth)
|
||||||
|
return a, key, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks *KeyStore) expire(addr common.Address, u *unlocked, timeout time.Duration) {
|
||||||
|
t := time.NewTimer(timeout)
|
||||||
|
defer t.Stop()
|
||||||
|
select {
|
||||||
|
case <-u.abort:
|
||||||
|
// just quit
|
||||||
|
case <-t.C:
|
||||||
|
ks.mu.Lock()
|
||||||
|
// only drop if it's still the same key instance that dropLater
|
||||||
|
// was launched with. we can check that using pointer equality
|
||||||
|
// because the map stores a new pointer every time the key is
|
||||||
|
// unlocked.
|
||||||
|
if ks.unlocked[addr] == u {
|
||||||
|
zeroKey(u.PrivateKey)
|
||||||
|
delete(ks.unlocked, addr)
|
||||||
|
}
|
||||||
|
ks.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccount generates a new key and stores it into the key directory,
|
||||||
|
// encrypting it with the passphrase.
|
||||||
|
func (ks *KeyStore) NewAccount(passphrase string) (accounts.Account, error) {
|
||||||
|
_, account, err := storeNewKey(ks.storage, crand.Reader, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return accounts.Account{}, err
|
||||||
|
}
|
||||||
|
// Add the account to the cache immediately rather
|
||||||
|
// than waiting for file system notifications to pick it up.
|
||||||
|
ks.cache.add(account)
|
||||||
|
ks.refreshWallets()
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export exports as a JSON key, encrypted with newPassphrase.
|
||||||
|
func (ks *KeyStore) Export(a accounts.Account, passphrase, newPassphrase string) (keyJSON []byte, err error) {
|
||||||
|
_, key, err := ks.getDecryptedKey(a, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var N, P int
|
||||||
|
if store, ok := ks.storage.(*keyStorePassphrase); ok {
|
||||||
|
N, P = store.scryptN, store.scryptP
|
||||||
|
} else {
|
||||||
|
N, P = StandardScryptN, StandardScryptP
|
||||||
|
}
|
||||||
|
return EncryptKey(key, newPassphrase, N, P)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import stores the given encrypted JSON key into the key directory.
|
||||||
|
func (ks *KeyStore) Import(keyJSON []byte, passphrase, newPassphrase string) (accounts.Account, error) {
|
||||||
|
key, err := DecryptKey(keyJSON, passphrase)
|
||||||
|
if key != nil && key.PrivateKey != nil {
|
||||||
|
defer zeroKey(key.PrivateKey)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return accounts.Account{}, err
|
||||||
|
}
|
||||||
|
return ks.importKey(key, newPassphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportECDSA stores the given key into the key directory, encrypting it with the passphrase.
|
||||||
|
func (ks *KeyStore) ImportECDSA(priv *ecdsa.PrivateKey, passphrase string) (accounts.Account, error) {
|
||||||
|
key := newKeyFromECDSA(priv)
|
||||||
|
if ks.cache.hasAddress(key.Address) {
|
||||||
|
return accounts.Account{}, fmt.Errorf("account already exists")
|
||||||
|
}
|
||||||
|
return ks.importKey(key, passphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ks *KeyStore) importKey(key *Key, passphrase string) (accounts.Account, error) {
|
||||||
|
a := accounts.Account{Address: key.Address, URL: accounts.URL{Scheme: KeyStoreScheme, Path: ks.storage.JoinPath(keyFileName(key.Address))}}
|
||||||
|
if err := ks.storage.StoreKey(a.URL.Path, key, passphrase); err != nil {
|
||||||
|
return accounts.Account{}, err
|
||||||
|
}
|
||||||
|
ks.cache.add(a)
|
||||||
|
ks.refreshWallets()
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update changes the passphrase of an existing account.
|
||||||
|
func (ks *KeyStore) Update(a accounts.Account, passphrase, newPassphrase string) error {
|
||||||
|
a, key, err := ks.getDecryptedKey(a, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ks.storage.StoreKey(a.URL.Path, key, newPassphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores
|
||||||
|
// a key file in the key directory. The key file is encrypted with the same passphrase.
|
||||||
|
func (ks *KeyStore) ImportPreSaleKey(keyJSON []byte, passphrase string) (accounts.Account, error) {
|
||||||
|
a, _, err := importPreSaleKey(ks.storage, keyJSON, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return a, err
|
||||||
|
}
|
||||||
|
ks.cache.add(a)
|
||||||
|
ks.refreshWallets()
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// zeroKey zeroes a private key in memory.
|
||||||
|
func zeroKey(k *ecdsa.PrivateKey) {
|
||||||
|
b := k.D.Bits()
|
||||||
|
for i := range b {
|
||||||
|
b[i] = 0
|
||||||
|
}
|
||||||
|
}
|
387
accounts/keystore/keystore_test.go
Normal file
387
accounts/keystore/keystore_test.go
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
// Copyright 2017 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 keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testSigData = make([]byte, 32)
|
||||||
|
|
||||||
|
func TestKeyStore(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStore(t, true)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
a, err := ks.NewAccount("foo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(a.URL.Path, dir) {
|
||||||
|
t.Errorf("account file %s doesn't have dir prefix", a.URL)
|
||||||
|
}
|
||||||
|
stat, err := os.Stat(a.URL.Path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("account file %s doesn't exist (%v)", a.URL, err)
|
||||||
|
}
|
||||||
|
if runtime.GOOS != "windows" && stat.Mode() != 0600 {
|
||||||
|
t.Fatalf("account file has wrong mode: got %o, want %o", stat.Mode(), 0600)
|
||||||
|
}
|
||||||
|
if !ks.HasAddress(a.Address) {
|
||||||
|
t.Errorf("HasAccount(%x) should've returned true", a.Address)
|
||||||
|
}
|
||||||
|
if err := ks.Update(a, "foo", "bar"); err != nil {
|
||||||
|
t.Errorf("Update error: %v", err)
|
||||||
|
}
|
||||||
|
if err := ks.Delete(a, "bar"); err != nil {
|
||||||
|
t.Errorf("Delete error: %v", err)
|
||||||
|
}
|
||||||
|
if common.FileExist(a.URL.Path) {
|
||||||
|
t.Errorf("account file %s should be gone after Delete", a.URL)
|
||||||
|
}
|
||||||
|
if ks.HasAddress(a.Address) {
|
||||||
|
t.Errorf("HasAccount(%x) should've returned true after Delete", a.Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSign(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStore(t, true)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
pass := "" // not used but required by API
|
||||||
|
a1, err := ks.NewAccount(pass)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := ks.Unlock(a1, ""); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSignWithPassphrase(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStore(t, true)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
pass := "passwd"
|
||||||
|
acc, err := ks.NewAccount(pass)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, unlocked := ks.unlocked[acc.Address]; unlocked {
|
||||||
|
t.Fatal("expected account to be locked")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = ks.SignHashWithPassphrase(acc, pass, testSigData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, unlocked := ks.unlocked[acc.Address]; unlocked {
|
||||||
|
t.Fatal("expected account to be locked")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = ks.SignHashWithPassphrase(acc, "invalid passwd", testSigData); err == nil {
|
||||||
|
t.Fatal("expected SignHashWithPassphrase to fail with invalid password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimedUnlock(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStore(t, true)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
pass := "foo"
|
||||||
|
a1, err := ks.NewAccount(pass)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signing without passphrase fails because account is locked
|
||||||
|
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||||
|
if err != ErrLocked {
|
||||||
|
t.Fatal("Signing should've failed with ErrLocked before unlocking, got ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signing with passphrase works
|
||||||
|
if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signing without passphrase works because account is temp unlocked
|
||||||
|
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signing fails again after automatic locking
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||||
|
if err != ErrLocked {
|
||||||
|
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverrideUnlock(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStore(t, false)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
pass := "foo"
|
||||||
|
a1, err := ks.NewAccount(pass)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlock indefinitely.
|
||||||
|
if err = ks.TimedUnlock(a1, pass, 5*time.Minute); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signing without passphrase works because account is temp unlocked
|
||||||
|
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset unlock to a shorter period, invalidates the previous unlock
|
||||||
|
if err = ks.TimedUnlock(a1, pass, 100*time.Millisecond); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signing without passphrase still works because account is temp unlocked
|
||||||
|
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Signing shouldn't return an error after unlocking, got ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signing fails again after automatic locking
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
_, err = ks.SignHash(accounts.Account{Address: a1.Address}, testSigData)
|
||||||
|
if err != ErrLocked {
|
||||||
|
t.Fatal("Signing should've failed with ErrLocked timeout expired, got ", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test should fail under -race if signing races the expiration goroutine.
|
||||||
|
func TestSignRace(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStore(t, false)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
// Create a test account.
|
||||||
|
a1, err := ks.NewAccount("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not create the test account", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ks.TimedUnlock(a1, "", 15*time.Millisecond); err != nil {
|
||||||
|
t.Fatal("could not unlock the test account", err)
|
||||||
|
}
|
||||||
|
end := time.Now().Add(500 * time.Millisecond)
|
||||||
|
for time.Now().Before(end) {
|
||||||
|
if _, err := ks.SignHash(accounts.Account{Address: a1.Address}, testSigData); err == ErrLocked {
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
t.Errorf("Sign error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
}
|
||||||
|
t.Errorf("Account did not lock within the timeout")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that the wallet notifier loop starts and stops correctly based on the
|
||||||
|
// addition and removal of wallet event subscriptions.
|
||||||
|
func TestWalletNotifierLifecycle(t *testing.T) {
|
||||||
|
// Create a temporary kesytore to test with
|
||||||
|
dir, ks := tmpKeyStore(t, false)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
// Ensure that the notification updater is not running yet
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
ks.mu.RLock()
|
||||||
|
updating := ks.updating
|
||||||
|
ks.mu.RUnlock()
|
||||||
|
|
||||||
|
if updating {
|
||||||
|
t.Errorf("wallet notifier running without subscribers")
|
||||||
|
}
|
||||||
|
// Subscribe to the wallet feed and ensure the updater boots up
|
||||||
|
updates := make(chan accounts.WalletEvent)
|
||||||
|
|
||||||
|
subs := make([]event.Subscription, 2)
|
||||||
|
for i := 0; i < len(subs); i++ {
|
||||||
|
// Create a new subscription
|
||||||
|
subs[i] = ks.Subscribe(updates)
|
||||||
|
|
||||||
|
// Ensure the notifier comes online
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
ks.mu.RLock()
|
||||||
|
updating = ks.updating
|
||||||
|
ks.mu.RUnlock()
|
||||||
|
|
||||||
|
if !updating {
|
||||||
|
t.Errorf("sub %d: wallet notifier not running after subscription", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Unsubscribe and ensure the updater terminates eventually
|
||||||
|
for i := 0; i < len(subs); i++ {
|
||||||
|
// Close an existing subscription
|
||||||
|
subs[i].Unsubscribe()
|
||||||
|
|
||||||
|
// Ensure the notifier shuts down at and only at the last close
|
||||||
|
for k := 0; k < int(walletRefreshCycle/(250*time.Millisecond))+2; k++ {
|
||||||
|
ks.mu.RLock()
|
||||||
|
updating = ks.updating
|
||||||
|
ks.mu.RUnlock()
|
||||||
|
|
||||||
|
if i < len(subs)-1 && !updating {
|
||||||
|
t.Fatalf("sub %d: event notifier stopped prematurely", i)
|
||||||
|
}
|
||||||
|
if i == len(subs)-1 && !updating {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Errorf("wallet notifier didn't terminate after unsubscribe")
|
||||||
|
}
|
||||||
|
|
||||||
|
type walletEvent struct {
|
||||||
|
accounts.WalletEvent
|
||||||
|
a accounts.Account
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that wallet notifications and correctly fired when accounts are added
|
||||||
|
// or deleted from the keystore.
|
||||||
|
func TestWalletNotifications(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStore(t, false)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
// Subscribe to the wallet feed and collect events.
|
||||||
|
var (
|
||||||
|
events []walletEvent
|
||||||
|
updates = make(chan accounts.WalletEvent)
|
||||||
|
sub = ks.Subscribe(updates)
|
||||||
|
)
|
||||||
|
defer sub.Unsubscribe()
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case ev := <-updates:
|
||||||
|
events = append(events, walletEvent{ev, ev.Wallet.Accounts()[0]})
|
||||||
|
case <-sub.Err():
|
||||||
|
close(updates)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Randomly add and remove accounts.
|
||||||
|
var (
|
||||||
|
live = make(map[common.Address]accounts.Account)
|
||||||
|
wantEvents []walletEvent
|
||||||
|
)
|
||||||
|
for i := 0; i < 1024; i++ {
|
||||||
|
if create := len(live) == 0 || rand.Int()%4 > 0; create {
|
||||||
|
// Add a new account and ensure wallet notifications arrives
|
||||||
|
account, err := ks.NewAccount("")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create test account: %v", err)
|
||||||
|
}
|
||||||
|
live[account.Address] = account
|
||||||
|
wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletArrived}, account})
|
||||||
|
} else {
|
||||||
|
// Delete a random account.
|
||||||
|
var account accounts.Account
|
||||||
|
for _, a := range live {
|
||||||
|
account = a
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := ks.Delete(account, ""); err != nil {
|
||||||
|
t.Fatalf("failed to delete test account: %v", err)
|
||||||
|
}
|
||||||
|
delete(live, account.Address)
|
||||||
|
wantEvents = append(wantEvents, walletEvent{accounts.WalletEvent{Kind: accounts.WalletDropped}, account})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shut down the event collector and check events.
|
||||||
|
sub.Unsubscribe()
|
||||||
|
<-updates
|
||||||
|
checkAccounts(t, live, ks.Wallets())
|
||||||
|
checkEvents(t, wantEvents, events)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkAccounts checks that all known live accounts are present in the wallet list.
|
||||||
|
func checkAccounts(t *testing.T, live map[common.Address]accounts.Account, wallets []accounts.Wallet) {
|
||||||
|
if len(live) != len(wallets) {
|
||||||
|
t.Errorf("wallet list doesn't match required accounts: have %d, want %d", len(wallets), len(live))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
liveList := make([]accounts.Account, 0, len(live))
|
||||||
|
for _, account := range live {
|
||||||
|
liveList = append(liveList, account)
|
||||||
|
}
|
||||||
|
sort.Sort(accountsByURL(liveList))
|
||||||
|
for j, wallet := range wallets {
|
||||||
|
if accs := wallet.Accounts(); len(accs) != 1 {
|
||||||
|
t.Errorf("wallet %d: contains invalid number of accounts: have %d, want 1", j, len(accs))
|
||||||
|
} else if accs[0] != liveList[j] {
|
||||||
|
t.Errorf("wallet %d: account mismatch: have %v, want %v", j, accs[0], liveList[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkEvents checks that all events in 'want' are present in 'have'. Events may be present multiple times.
|
||||||
|
func checkEvents(t *testing.T, want []walletEvent, have []walletEvent) {
|
||||||
|
for _, wantEv := range want {
|
||||||
|
nmatch := 0
|
||||||
|
for ; len(have) > 0; nmatch++ {
|
||||||
|
if have[0].Kind != wantEv.Kind || have[0].a != wantEv.a {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
have = have[1:]
|
||||||
|
}
|
||||||
|
if nmatch == 0 {
|
||||||
|
t.Fatalf("can't find event with Kind=%v for %x", wantEv.Kind, wantEv.a.Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tmpKeyStore(t *testing.T, encrypted bool) (string, *KeyStore) {
|
||||||
|
d, err := ioutil.TempDir("", "eth-keystore-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
newKs := NewPlaintextKeyStore
|
||||||
|
if encrypted {
|
||||||
|
newKs = func(kd string) *KeyStore { return NewKeyStore(kd, veryLightScryptN, veryLightScryptP) }
|
||||||
|
}
|
||||||
|
return d, newKs(d)
|
||||||
|
}
|
@ -123,6 +123,7 @@ func (ks keyStorePassphrase) StoreKey(filename string, key *Key, auth string) er
|
|||||||
"Please file a ticket at:\n\n" +
|
"Please file a ticket at:\n\n" +
|
||||||
"https://github.com/ethereum/go-ethereum/issues." +
|
"https://github.com/ethereum/go-ethereum/issues." +
|
||||||
"The error was : %s"
|
"The error was : %s"
|
||||||
|
//lint:ignore ST1005 This is a message for the user
|
||||||
return fmt.Errorf(msg, tmpName, err)
|
return fmt.Errorf(msg, tmpName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -237,7 +238,7 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) {
|
|||||||
|
|
||||||
func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
|
func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
|
||||||
if cryptoJson.Cipher != "aes-128-ctr" {
|
if cryptoJson.Cipher != "aes-128-ctr" {
|
||||||
return nil, fmt.Errorf("Cipher not supported: %v", cryptoJson.Cipher)
|
return nil, fmt.Errorf("cipher not supported: %v", cryptoJson.Cipher)
|
||||||
}
|
}
|
||||||
mac, err := hex.DecodeString(cryptoJson.MAC)
|
mac, err := hex.DecodeString(cryptoJson.MAC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -273,7 +274,7 @@ func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
|
|||||||
|
|
||||||
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
|
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
|
||||||
if keyProtected.Version != version {
|
if keyProtected.Version != version {
|
||||||
return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
|
return nil, nil, fmt.Errorf("version not supported: %v", keyProtected.Version)
|
||||||
}
|
}
|
||||||
keyId = uuid.Parse(keyProtected.Id)
|
keyId = uuid.Parse(keyProtected.Id)
|
||||||
plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
|
plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
|
||||||
@ -335,13 +336,13 @@ func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
|
|||||||
c := ensureInt(cryptoJSON.KDFParams["c"])
|
c := ensureInt(cryptoJSON.KDFParams["c"])
|
||||||
prf := cryptoJSON.KDFParams["prf"].(string)
|
prf := cryptoJSON.KDFParams["prf"].(string)
|
||||||
if prf != "hmac-sha256" {
|
if prf != "hmac-sha256" {
|
||||||
return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
|
return nil, fmt.Errorf("unsupported PBKDF2 PRF: %s", prf)
|
||||||
}
|
}
|
||||||
key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
|
key := pbkdf2.Key(authArray, salt, c, dkLen, sha256.New)
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
|
return nil, fmt.Errorf("unsupported KDF: %s", cryptoJSON.KDF)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: can we do without this when unmarshalling dynamic JSON?
|
// TODO: can we do without this when unmarshalling dynamic JSON?
|
60
accounts/keystore/passphrase_test.go
Normal file
60
accounts/keystore/passphrase_test.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// 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 keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
veryLightScryptN = 2
|
||||||
|
veryLightScryptP = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests that a json key file can be decrypted and encrypted in multiple rounds.
|
||||||
|
func TestKeyEncryptDecrypt(t *testing.T) {
|
||||||
|
keyjson, err := ioutil.ReadFile("testdata/very-light-scrypt.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
password := ""
|
||||||
|
address := common.HexToAddress("45dea0fb0bba44f4fcf290bba71fd57d7117cbb8")
|
||||||
|
|
||||||
|
// Do a few rounds of decryption and encryption
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
// Try a bad password first
|
||||||
|
if _, err := DecryptKey(keyjson, password+"bad"); err == nil {
|
||||||
|
t.Errorf("test %d: json key decrypted with bad password", i)
|
||||||
|
}
|
||||||
|
// Decrypt with the correct password
|
||||||
|
key, err := DecryptKey(keyjson, password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test %d: json key failed to decrypt: %v", i, err)
|
||||||
|
}
|
||||||
|
if key.Address != address {
|
||||||
|
t.Errorf("test %d: key address mismatch: have %x, want %x", i, key.Address, address)
|
||||||
|
}
|
||||||
|
// Recrypt with a new password and start over
|
||||||
|
password += "new data appended"
|
||||||
|
if keyjson, err = EncryptKey(key, password, veryLightScryptN, veryLightScryptP); err != nil {
|
||||||
|
t.Errorf("test %d: failed to recrypt key %v", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
266
accounts/keystore/plain_test.go
Normal file
266
accounts/keystore/plain_test.go
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
// Copyright 2014 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 keystore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {
|
||||||
|
d, err := ioutil.TempDir("", "geth-keystore-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if encrypted {
|
||||||
|
ks = &keyStorePassphrase{d, veryLightScryptN, veryLightScryptP, true}
|
||||||
|
} else {
|
||||||
|
ks = &keyStorePlain{d}
|
||||||
|
}
|
||||||
|
return d, ks
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyStorePlain(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStoreIface(t, false)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
pass := "" // not used but required by API
|
||||||
|
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(k1.Address, k2.Address) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyStorePassphrase(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStoreIface(t, true)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
pass := "foo"
|
||||||
|
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
k2, err := ks.GetKey(k1.Address, account.URL.Path, pass)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(k1.Address, k2.Address) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(k1.PrivateKey, k2.PrivateKey) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyStorePassphraseDecryptionFail(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStoreIface(t, true)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
pass := "foo"
|
||||||
|
k1, account, err := storeNewKey(ks, rand.Reader, pass)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = ks.GetKey(k1.Address, account.URL.Path, "bar"); err != ErrDecrypt {
|
||||||
|
t.Fatalf("wrong error for invalid password\ngot %q\nwant %q", err, ErrDecrypt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImportPreSaleKey(t *testing.T) {
|
||||||
|
dir, ks := tmpKeyStoreIface(t, true)
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
// file content of a presale key file generated with:
|
||||||
|
// python pyethsaletool.py genwallet
|
||||||
|
// with password "foo"
|
||||||
|
fileContent := "{\"encseed\": \"26d87f5f2bf9835f9a47eefae571bc09f9107bb13d54ff12a4ec095d01f83897494cf34f7bed2ed34126ecba9db7b62de56c9d7cd136520a0427bfb11b8954ba7ac39b90d4650d3448e31185affcd74226a68f1e94b1108e6e0a4a91cdd83eba\", \"ethaddr\": \"d4584b5f6229b7be90727b0fc8c6b91bb427821f\", \"email\": \"gustav.simonsson@gmail.com\", \"btcaddr\": \"1EVknXyFC68kKNLkh6YnKzW41svSRoaAcx\"}"
|
||||||
|
pass := "foo"
|
||||||
|
account, _, err := importPreSaleKey(ks, []byte(fileContent), pass)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if account.Address != common.HexToAddress("d4584b5f6229b7be90727b0fc8c6b91bb427821f") {
|
||||||
|
t.Errorf("imported account has wrong address %x", account.Address)
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(account.URL.Path, dir) {
|
||||||
|
t.Errorf("imported account file not in keystore directory: %q", account.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test and utils for the key store tests in the Ethereum JSON tests;
|
||||||
|
// testdataKeyStoreTests/basic_tests.json
|
||||||
|
type KeyStoreTestV3 struct {
|
||||||
|
Json encryptedKeyJSONV3
|
||||||
|
Password string
|
||||||
|
Priv string
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyStoreTestV1 struct {
|
||||||
|
Json encryptedKeyJSONV1
|
||||||
|
Password string
|
||||||
|
Priv string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV3_PBKDF2_1(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
|
||||||
|
testDecryptV3(tests["wikipage_test_vector_pbkdf2"], t)
|
||||||
|
}
|
||||||
|
|
||||||
|
var testsSubmodule = filepath.Join("..", "..", "tests", "testdata", "KeyStoreTests")
|
||||||
|
|
||||||
|
func skipIfSubmoduleMissing(t *testing.T) {
|
||||||
|
if !common.FileExist(testsSubmodule) {
|
||||||
|
t.Skipf("can't find JSON tests from submodule at %s", testsSubmodule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV3_PBKDF2_2(t *testing.T) {
|
||||||
|
skipIfSubmoduleMissing(t)
|
||||||
|
t.Parallel()
|
||||||
|
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||||
|
testDecryptV3(tests["test1"], t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV3_PBKDF2_3(t *testing.T) {
|
||||||
|
skipIfSubmoduleMissing(t)
|
||||||
|
t.Parallel()
|
||||||
|
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||||
|
testDecryptV3(tests["python_generated_test_with_odd_iv"], t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV3_PBKDF2_4(t *testing.T) {
|
||||||
|
skipIfSubmoduleMissing(t)
|
||||||
|
t.Parallel()
|
||||||
|
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||||
|
testDecryptV3(tests["evilnonce"], t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV3_Scrypt_1(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
|
||||||
|
testDecryptV3(tests["wikipage_test_vector_scrypt"], t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV3_Scrypt_2(t *testing.T) {
|
||||||
|
skipIfSubmoduleMissing(t)
|
||||||
|
t.Parallel()
|
||||||
|
tests := loadKeyStoreTestV3(filepath.Join(testsSubmodule, "basic_tests.json"), t)
|
||||||
|
testDecryptV3(tests["test2"], t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1_1(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := loadKeyStoreTestV1("testdata/v1_test_vector.json", t)
|
||||||
|
testDecryptV1(tests["test1"], t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV1_2(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ks := &keyStorePassphrase{"testdata/v1", LightScryptN, LightScryptP, true}
|
||||||
|
addr := common.HexToAddress("cb61d5a9c4896fb9658090b597ef0e7be6f7b67e")
|
||||||
|
file := "testdata/v1/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e/cb61d5a9c4896fb9658090b597ef0e7be6f7b67e"
|
||||||
|
k, err := ks.GetKey(addr, file, "g")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
privHex := hex.EncodeToString(crypto.FromECDSA(k.PrivateKey))
|
||||||
|
expectedHex := "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d"
|
||||||
|
if privHex != expectedHex {
|
||||||
|
t.Fatal(fmt.Errorf("Unexpected privkey: %v, expected %v", privHex, expectedHex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDecryptV3(test KeyStoreTestV3, t *testing.T) {
|
||||||
|
privBytes, _, err := decryptKeyV3(&test.Json, test.Password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
privHex := hex.EncodeToString(privBytes)
|
||||||
|
if test.Priv != privHex {
|
||||||
|
t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDecryptV1(test KeyStoreTestV1, t *testing.T) {
|
||||||
|
privBytes, _, err := decryptKeyV1(&test.Json, test.Password)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
privHex := hex.EncodeToString(privBytes)
|
||||||
|
if test.Priv != privHex {
|
||||||
|
t.Fatal(fmt.Errorf("Decrypted bytes not equal to test, expected %v have %v", test.Priv, privHex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadKeyStoreTestV3(file string, t *testing.T) map[string]KeyStoreTestV3 {
|
||||||
|
tests := make(map[string]KeyStoreTestV3)
|
||||||
|
err := common.LoadJSON(file, &tests)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return tests
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadKeyStoreTestV1(file string, t *testing.T) map[string]KeyStoreTestV1 {
|
||||||
|
tests := make(map[string]KeyStoreTestV1)
|
||||||
|
err := common.LoadJSON(file, &tests)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return tests
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyForDirectICAP(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
key := NewKeyForDirectICAP(rand.Reader)
|
||||||
|
if !strings.HasPrefix(key.Address.Hex(), "0x00") {
|
||||||
|
t.Errorf("Expected first address byte to be zero, have: %s", key.Address.Hex())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV3_31_Byte_Key(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
|
||||||
|
testDecryptV3(tests["31_byte_key"], t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestV3_30_Byte_Key(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := loadKeyStoreTestV3("testdata/v3_test_vector.json", t)
|
||||||
|
testDecryptV3(tests["30_byte_key"], t)
|
||||||
|
}
|
1
accounts/keystore/testdata/dupes/1
vendored
Normal file
1
accounts/keystore/testdata/dupes/1
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
1
accounts/keystore/testdata/dupes/2
vendored
Normal file
1
accounts/keystore/testdata/dupes/2
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
1
accounts/keystore/testdata/dupes/foo
vendored
Normal file
1
accounts/keystore/testdata/dupes/foo
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3}
|
1
accounts/keystore/testdata/keystore/.hiddenfile
vendored
Normal file
1
accounts/keystore/testdata/keystore/.hiddenfile
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
21
accounts/keystore/testdata/keystore/README
vendored
Normal file
21
accounts/keystore/testdata/keystore/README
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
This directory contains accounts for testing.
|
||||||
|
The password that unlocks them is "foobar".
|
||||||
|
|
||||||
|
The "good" key files which are supposed to be loadable are:
|
||||||
|
|
||||||
|
- File: UTC--2016-03-22T12-57-55.920751759Z--7ef5a6135f1fd6a02593eedc869c6d41d934aef8
|
||||||
|
Address: 0x7ef5a6135f1fd6a02593eedc869c6d41d934aef8
|
||||||
|
- File: aaa
|
||||||
|
Address: 0xf466859ead1932d743d622cb74fc058882e8648a
|
||||||
|
- File: zzz
|
||||||
|
Address: 0x289d485d9771714cce91d3393d764e1311907acc
|
||||||
|
|
||||||
|
The other files (including this README) are broken in various ways
|
||||||
|
and should not be picked up by package accounts:
|
||||||
|
|
||||||
|
- File: no-address (missing address field, otherwise same as "aaa")
|
||||||
|
- File: garbage (file with random data)
|
||||||
|
- File: empty (file with no content)
|
||||||
|
- File: swapfile~ (should be skipped)
|
||||||
|
- File: .hiddenfile (should be skipped)
|
||||||
|
- File: foo/... (should be skipped because it is a directory)
|
@ -0,0 +1 @@
|
|||||||
|
{"address":"7ef5a6135f1fd6a02593eedc869c6d41d934aef8","crypto":{"cipher":"aes-128-ctr","ciphertext":"1d0839166e7a15b9c1333fc865d69858b22df26815ccf601b28219b6192974e1","cipherparams":{"iv":"8df6caa7ff1b00c4e871f002cb7921ed"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"e5e6ef3f4ea695f496b643ebd3f75c0aa58ef4070e90c80c5d3fb0241bf1595c"},"mac":"6d16dfde774845e4585357f24bce530528bc69f4f84e1e22880d34fa45c273e5"},"id":"950077c7-71e3-4c44-a4a1-143919141ed4","version":3}
|
1
accounts/keystore/testdata/keystore/aaa
vendored
Normal file
1
accounts/keystore/testdata/keystore/aaa
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"address":"f466859ead1932d743d622cb74fc058882e8648a","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
1
accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e
vendored
Normal file
1
accounts/keystore/testdata/keystore/foo/fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"address":"fd9bd350f08ee3c0c19b85a8e16114a11a60aa4e","crypto":{"cipher":"aes-128-ctr","ciphertext":"8124d5134aa4a927c79fd852989e4b5419397566f04b0936a1eb1d168c7c68a5","cipherparams":{"iv":"e2febe17176414dd2cda28287947eb2f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"44b415ede89f3bdd6830390a21b78965f571b347a589d1d943029f016c5e8bd5"},"mac":"5e149ff25bfd9dd45746a84bb2bcd2f015f2cbca2b6d25c5de8c29617f71fe5b"},"id":"d6ac5452-2b2c-4d3c-ad80-4bf0327d971c","version":3}
|
BIN
accounts/keystore/testdata/keystore/garbage
vendored
Normal file
BIN
accounts/keystore/testdata/keystore/garbage
vendored
Normal file
Binary file not shown.
1
accounts/keystore/testdata/keystore/no-address
vendored
Normal file
1
accounts/keystore/testdata/keystore/no-address
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
1
accounts/keystore/testdata/keystore/zero
vendored
Normal file
1
accounts/keystore/testdata/keystore/zero
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"address":"0000000000000000000000000000000000000000","crypto":{"cipher":"aes-128-ctr","ciphertext":"cb664472deacb41a2e995fa7f96fe29ce744471deb8d146a0e43c7898c9ddd4d","cipherparams":{"iv":"dfd9ee70812add5f4b8f89d0811c9158"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"0d6769bf016d45c479213990d6a08d938469c4adad8a02ce507b4a4e7b7739f1"},"mac":"bac9af994b15a45dd39669fc66f9aa8a3b9dd8c22cb16e4d8d7ea089d0f1a1a9"},"id":"472e8b3d-afb6-45b5-8111-72c89895099a","version":3}
|
1
accounts/keystore/testdata/keystore/zzz
vendored
Normal file
1
accounts/keystore/testdata/keystore/zzz
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"address":"289d485d9771714cce91d3393d764e1311907acc","crypto":{"cipher":"aes-128-ctr","ciphertext":"faf32ca89d286b107f5e6d842802e05263c49b78d46eac74e6109e9a963378ab","cipherparams":{"iv":"558833eec4a665a8c55608d7d503407d"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":8,"p":16,"r":8,"salt":"d571fff447ffb24314f9513f5160246f09997b857ac71348b73e785aab40dc04"},"mac":"21edb85ff7d0dab1767b9bf498f2c3cb7be7609490756bd32300bb213b59effe"},"id":"3279afcf-55ba-43ff-8997-02dcc46a6525","version":3}
|
@ -0,0 +1 @@
|
|||||||
|
{"address":"cb61d5a9c4896fb9658090b597ef0e7be6f7b67e","Crypto":{"cipher":"aes-128-cbc","ciphertext":"6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0","cipherparams":{"iv":"35337770fc2117994ecdcad026bccff4"},"kdf":"scrypt","kdfparams":{"n":262144,"r":8,"p":1,"dklen":32,"salt":"9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"},"mac":"3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644","version":"1"},"id":"e25f7c1f-d318-4f29-b62c-687190d4d299","version":"1"}
|
28
accounts/keystore/testdata/v1_test_vector.json
vendored
Normal file
28
accounts/keystore/testdata/v1_test_vector.json
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"test1": {
|
||||||
|
"json": {
|
||||||
|
"Crypto": {
|
||||||
|
"cipher": "aes-128-cbc",
|
||||||
|
"cipherparams": {
|
||||||
|
"iv": "35337770fc2117994ecdcad026bccff4"
|
||||||
|
},
|
||||||
|
"ciphertext": "6143d3192db8b66eabd693d9c4e414dcfaee52abda451af79ccf474dafb35f1bfc7ea013aa9d2ee35969a1a2e8d752d0",
|
||||||
|
"kdf": "scrypt",
|
||||||
|
"kdfparams": {
|
||||||
|
"dklen": 32,
|
||||||
|
"n": 262144,
|
||||||
|
"p": 1,
|
||||||
|
"r": 8,
|
||||||
|
"salt": "9afcddebca541253a2f4053391c673ff9fe23097cd8555d149d929e4ccf1257f"
|
||||||
|
},
|
||||||
|
"mac": "3f3d5af884b17a100b0b3232c0636c230a54dc2ac8d986227219b0dd89197644",
|
||||||
|
"version": "1"
|
||||||
|
},
|
||||||
|
"address": "cb61d5a9c4896fb9658090b597ef0e7be6f7b67e",
|
||||||
|
"id": "e25f7c1f-d318-4f29-b62c-687190d4d299",
|
||||||
|
"version": "1"
|
||||||
|
},
|
||||||
|
"password": "g",
|
||||||
|
"priv": "d1b1178d3529626a1a93e073f65028370d14c7eb0936eb42abef05db6f37ad7d"
|
||||||
|
}
|
||||||
|
}
|
97
accounts/keystore/testdata/v3_test_vector.json
vendored
Normal file
97
accounts/keystore/testdata/v3_test_vector.json
vendored
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
"wikipage_test_vector_scrypt": {
|
||||||
|
"json": {
|
||||||
|
"crypto" : {
|
||||||
|
"cipher" : "aes-128-ctr",
|
||||||
|
"cipherparams" : {
|
||||||
|
"iv" : "83dbcc02d8ccb40e466191a123791e0e"
|
||||||
|
},
|
||||||
|
"ciphertext" : "d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c",
|
||||||
|
"kdf" : "scrypt",
|
||||||
|
"kdfparams" : {
|
||||||
|
"dklen" : 32,
|
||||||
|
"n" : 262144,
|
||||||
|
"r" : 1,
|
||||||
|
"p" : 8,
|
||||||
|
"salt" : "ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19"
|
||||||
|
},
|
||||||
|
"mac" : "2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097"
|
||||||
|
},
|
||||||
|
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
|
||||||
|
"version" : 3
|
||||||
|
},
|
||||||
|
"password": "testpassword",
|
||||||
|
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
|
||||||
|
},
|
||||||
|
"wikipage_test_vector_pbkdf2": {
|
||||||
|
"json": {
|
||||||
|
"crypto" : {
|
||||||
|
"cipher" : "aes-128-ctr",
|
||||||
|
"cipherparams" : {
|
||||||
|
"iv" : "6087dab2f9fdbbfaddc31a909735c1e6"
|
||||||
|
},
|
||||||
|
"ciphertext" : "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
|
||||||
|
"kdf" : "pbkdf2",
|
||||||
|
"kdfparams" : {
|
||||||
|
"c" : 262144,
|
||||||
|
"dklen" : 32,
|
||||||
|
"prf" : "hmac-sha256",
|
||||||
|
"salt" : "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
|
||||||
|
},
|
||||||
|
"mac" : "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"
|
||||||
|
},
|
||||||
|
"id" : "3198bc9c-6672-5ab3-d995-4942343ae5b6",
|
||||||
|
"version" : 3
|
||||||
|
},
|
||||||
|
"password": "testpassword",
|
||||||
|
"priv": "7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"
|
||||||
|
},
|
||||||
|
"31_byte_key": {
|
||||||
|
"json": {
|
||||||
|
"crypto" : {
|
||||||
|
"cipher" : "aes-128-ctr",
|
||||||
|
"cipherparams" : {
|
||||||
|
"iv" : "e0c41130a323adc1446fc82f724bca2f"
|
||||||
|
},
|
||||||
|
"ciphertext" : "9517cd5bdbe69076f9bf5057248c6c050141e970efa36ce53692d5d59a3984",
|
||||||
|
"kdf" : "scrypt",
|
||||||
|
"kdfparams" : {
|
||||||
|
"dklen" : 32,
|
||||||
|
"n" : 2,
|
||||||
|
"r" : 8,
|
||||||
|
"p" : 1,
|
||||||
|
"salt" : "711f816911c92d649fb4c84b047915679933555030b3552c1212609b38208c63"
|
||||||
|
},
|
||||||
|
"mac" : "d5e116151c6aa71470e67a7d42c9620c75c4d23229847dcc127794f0732b0db5"
|
||||||
|
},
|
||||||
|
"id" : "fecfc4ce-e956-48fd-953b-30f8b52ed66c",
|
||||||
|
"version" : 3
|
||||||
|
},
|
||||||
|
"password": "foo",
|
||||||
|
"priv": "fa7b3db73dc7dfdf8c5fbdb796d741e4488628c41fc4febd9160a866ba0f35"
|
||||||
|
},
|
||||||
|
"30_byte_key": {
|
||||||
|
"json": {
|
||||||
|
"crypto" : {
|
||||||
|
"cipher" : "aes-128-ctr",
|
||||||
|
"cipherparams" : {
|
||||||
|
"iv" : "3ca92af36ad7c2cd92454c59cea5ef00"
|
||||||
|
},
|
||||||
|
"ciphertext" : "108b7d34f3442fc26ab1ab90ca91476ba6bfa8c00975a49ef9051dc675aa",
|
||||||
|
"kdf" : "scrypt",
|
||||||
|
"kdfparams" : {
|
||||||
|
"dklen" : 32,
|
||||||
|
"n" : 2,
|
||||||
|
"r" : 8,
|
||||||
|
"p" : 1,
|
||||||
|
"salt" : "d0769e608fb86cda848065642a9c6fa046845c928175662b8e356c77f914cd3b"
|
||||||
|
},
|
||||||
|
"mac" : "75d0e6759f7b3cefa319c3be41680ab6beea7d8328653474bd06706d4cc67420"
|
||||||
|
},
|
||||||
|
"id" : "a37e1559-5955-450d-8075-7b8931b392b2",
|
||||||
|
"version" : 3
|
||||||
|
},
|
||||||
|
"password": "foo",
|
||||||
|
"priv": "81c29e8142bb6a81bef5a92bda7a8328a5c85bb2f9542e76f9b0f94fc018"
|
||||||
|
}
|
||||||
|
}
|
1
accounts/keystore/testdata/very-light-scrypt.json
vendored
Normal file
1
accounts/keystore/testdata/very-light-scrypt.json
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"address":"45dea0fb0bba44f4fcf290bba71fd57d7117cbb8","crypto":{"cipher":"aes-128-ctr","ciphertext":"b87781948a1befd247bff51ef4063f716cf6c2d3481163e9a8f42e1f9bb74145","cipherparams":{"iv":"dc4926b48a105133d2f16b96833abf1e"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":2,"p":1,"r":8,"salt":"004244bbdc51cadda545b1cfa43cff9ed2ae88e08c61f1479dbb45410722f8f0"},"mac":"39990c1684557447940d4c69e06b1b82b2aceacb43f284df65c956daf3046b85"},"id":"ce541d8d-c79b-40f8-9f8c-20f59616faba","version":3}
|
229
accounts/manager.go
Normal file
229
accounts/manager.go
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
// Copyright 2017 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 accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/event"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config contains the settings of the global account manager.
|
||||||
|
//
|
||||||
|
// TODO(rjl493456442, karalabe, holiman): Get rid of this when account management
|
||||||
|
// is removed in favor of Clef.
|
||||||
|
type Config struct {
|
||||||
|
InsecureUnlockAllowed bool // Whether account unlocking in insecure environment is allowed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager is an overarching account manager that can communicate with various
|
||||||
|
// backends for signing transactions.
|
||||||
|
type Manager struct {
|
||||||
|
config *Config // Global account manager configurations
|
||||||
|
backends map[reflect.Type][]Backend // Index of backends currently registered
|
||||||
|
updaters []event.Subscription // Wallet update subscriptions for all backends
|
||||||
|
updates chan WalletEvent // Subscription sink for backend wallet changes
|
||||||
|
wallets []Wallet // Cache of all wallets from all registered backends
|
||||||
|
|
||||||
|
feed event.Feed // Wallet feed notifying of arrivals/departures
|
||||||
|
|
||||||
|
quit chan chan error
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager creates a generic account manager to sign transaction via various
|
||||||
|
// supported backends.
|
||||||
|
func NewManager(config *Config, backends ...Backend) *Manager {
|
||||||
|
// Retrieve the initial list of wallets from the backends and sort by URL
|
||||||
|
var wallets []Wallet
|
||||||
|
for _, backend := range backends {
|
||||||
|
wallets = merge(wallets, backend.Wallets()...)
|
||||||
|
}
|
||||||
|
// Subscribe to wallet notifications from all backends
|
||||||
|
updates := make(chan WalletEvent, 4*len(backends))
|
||||||
|
|
||||||
|
subs := make([]event.Subscription, len(backends))
|
||||||
|
for i, backend := range backends {
|
||||||
|
subs[i] = backend.Subscribe(updates)
|
||||||
|
}
|
||||||
|
// Assemble the account manager and return
|
||||||
|
am := &Manager{
|
||||||
|
config: config,
|
||||||
|
backends: make(map[reflect.Type][]Backend),
|
||||||
|
updaters: subs,
|
||||||
|
updates: updates,
|
||||||
|
wallets: wallets,
|
||||||
|
quit: make(chan chan error),
|
||||||
|
}
|
||||||
|
for _, backend := range backends {
|
||||||
|
kind := reflect.TypeOf(backend)
|
||||||
|
am.backends[kind] = append(am.backends[kind], backend)
|
||||||
|
}
|
||||||
|
go am.update()
|
||||||
|
|
||||||
|
return am
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close terminates the account manager's internal notification processes.
|
||||||
|
func (am *Manager) Close() error {
|
||||||
|
errc := make(chan error)
|
||||||
|
am.quit <- errc
|
||||||
|
return <-errc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config returns the configuration of account manager.
|
||||||
|
func (am *Manager) Config() *Config {
|
||||||
|
return am.config
|
||||||
|
}
|
||||||
|
|
||||||
|
// update is the wallet event loop listening for notifications from the backends
|
||||||
|
// and updating the cache of wallets.
|
||||||
|
func (am *Manager) update() {
|
||||||
|
// Close all subscriptions when the manager terminates
|
||||||
|
defer func() {
|
||||||
|
am.lock.Lock()
|
||||||
|
for _, sub := range am.updaters {
|
||||||
|
sub.Unsubscribe()
|
||||||
|
}
|
||||||
|
am.updaters = nil
|
||||||
|
am.lock.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Loop until termination
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-am.updates:
|
||||||
|
// Wallet event arrived, update local cache
|
||||||
|
am.lock.Lock()
|
||||||
|
switch event.Kind {
|
||||||
|
case WalletArrived:
|
||||||
|
am.wallets = merge(am.wallets, event.Wallet)
|
||||||
|
case WalletDropped:
|
||||||
|
am.wallets = drop(am.wallets, event.Wallet)
|
||||||
|
}
|
||||||
|
am.lock.Unlock()
|
||||||
|
|
||||||
|
// Notify any listeners of the event
|
||||||
|
am.feed.Send(event)
|
||||||
|
|
||||||
|
case errc := <-am.quit:
|
||||||
|
// Manager terminating, return
|
||||||
|
errc <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backends retrieves the backend(s) with the given type from the account manager.
|
||||||
|
func (am *Manager) Backends(kind reflect.Type) []Backend {
|
||||||
|
return am.backends[kind]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wallets returns all signer accounts registered under this account manager.
|
||||||
|
func (am *Manager) Wallets() []Wallet {
|
||||||
|
am.lock.RLock()
|
||||||
|
defer am.lock.RUnlock()
|
||||||
|
|
||||||
|
cpy := make([]Wallet, len(am.wallets))
|
||||||
|
copy(cpy, am.wallets)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wallet retrieves the wallet associated with a particular URL.
|
||||||
|
func (am *Manager) Wallet(url string) (Wallet, error) {
|
||||||
|
am.lock.RLock()
|
||||||
|
defer am.lock.RUnlock()
|
||||||
|
|
||||||
|
parsed, err := parseURL(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, wallet := range am.Wallets() {
|
||||||
|
if wallet.URL() == parsed {
|
||||||
|
return wallet, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, ErrUnknownWallet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accounts returns all account addresses of all wallets within the account manager
|
||||||
|
func (am *Manager) Accounts() []common.Address {
|
||||||
|
am.lock.RLock()
|
||||||
|
defer am.lock.RUnlock()
|
||||||
|
|
||||||
|
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
||||||
|
for _, wallet := range am.wallets {
|
||||||
|
for _, account := range wallet.Accounts() {
|
||||||
|
addresses = append(addresses, account.Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find attempts to locate the wallet corresponding to a specific account. Since
|
||||||
|
// accounts can be dynamically added to and removed from wallets, this method has
|
||||||
|
// a linear runtime in the number of wallets.
|
||||||
|
func (am *Manager) Find(account Account) (Wallet, error) {
|
||||||
|
am.lock.RLock()
|
||||||
|
defer am.lock.RUnlock()
|
||||||
|
|
||||||
|
for _, wallet := range am.wallets {
|
||||||
|
if wallet.Contains(account) {
|
||||||
|
return wallet, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, ErrUnknownAccount
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe creates an async subscription to receive notifications when the
|
||||||
|
// manager detects the arrival or departure of a wallet from any of its backends.
|
||||||
|
func (am *Manager) Subscribe(sink chan<- WalletEvent) event.Subscription {
|
||||||
|
return am.feed.Subscribe(sink)
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge is a sorted analogue of append for wallets, where the ordering of the
|
||||||
|
// origin list is preserved by inserting new wallets at the correct position.
|
||||||
|
//
|
||||||
|
// The original slice is assumed to be already sorted by URL.
|
||||||
|
func merge(slice []Wallet, wallets ...Wallet) []Wallet {
|
||||||
|
for _, wallet := range wallets {
|
||||||
|
n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
|
||||||
|
if n == len(slice) {
|
||||||
|
slice = append(slice, wallet)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slice = append(slice[:n], append([]Wallet{wallet}, slice[n:]...)...)
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// drop is the couterpart of merge, which looks up wallets from within the sorted
|
||||||
|
// cache and removes the ones specified.
|
||||||
|
func drop(slice []Wallet, wallets ...Wallet) []Wallet {
|
||||||
|
for _, wallet := range wallets {
|
||||||
|
n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
|
||||||
|
if n == len(slice) {
|
||||||
|
// Wallet not found, may happen during startup
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slice = append(slice[:n], slice[n+1:]...)
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
96
accounts/url_test.go
Normal file
96
accounts/url_test.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright 2018 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 accounts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestURLParsing(t *testing.T) {
|
||||||
|
url, err := parseURL("https://ethereum.org")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if url.Scheme != "https" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
||||||
|
}
|
||||||
|
if url.Path != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "ethereum.org", url.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = parseURL("ethereum.org")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected err, got: nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLString(t *testing.T) {
|
||||||
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
|
if url.String() != "https://ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https://ethereum.org", url.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
url = URL{Scheme: "", Path: "ethereum.org"}
|
||||||
|
if url.String() != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "ethereum.org", url.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLMarshalJSON(t *testing.T) {
|
||||||
|
url := URL{Scheme: "https", Path: "ethereum.org"}
|
||||||
|
json, err := url.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpcted error: %v", err)
|
||||||
|
}
|
||||||
|
if string(json) != "\"https://ethereum.org\"" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "\"https://ethereum.org\"", string(json))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLUnmarshalJSON(t *testing.T) {
|
||||||
|
url := &URL{}
|
||||||
|
err := url.UnmarshalJSON([]byte("\"https://ethereum.org\""))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpcted error: %v", err)
|
||||||
|
}
|
||||||
|
if url.Scheme != "https" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Scheme)
|
||||||
|
}
|
||||||
|
if url.Path != "ethereum.org" {
|
||||||
|
t.Errorf("expected: %v, got: %v", "https", url.Path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLComparison(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
urlA URL
|
||||||
|
urlB URL
|
||||||
|
expect int
|
||||||
|
}{
|
||||||
|
{URL{"https", "ethereum.org"}, URL{"https", "ethereum.org"}, 0},
|
||||||
|
{URL{"http", "ethereum.org"}, URL{"https", "ethereum.org"}, -1},
|
||||||
|
{URL{"https", "ethereum.org/a"}, URL{"https", "ethereum.org"}, 1},
|
||||||
|
{URL{"https", "abc.org"}, URL{"https", "ethereum.org"}, -1},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
result := tt.urlA.Cmp(tt.urlB)
|
||||||
|
if result != tt.expect {
|
||||||
|
t.Errorf("test %d: cmp mismatch: expected: %d, got: %d", i, tt.expect, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,12 +20,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/karalabe/hid"
|
"github.com/karalabe/usb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
||||||
@ -64,6 +65,7 @@ type Hub struct {
|
|||||||
// TODO(karalabe): remove if hotplug lands on Windows
|
// TODO(karalabe): remove if hotplug lands on Windows
|
||||||
commsPend int // Number of operations blocking enumeration
|
commsPend int // Number of operations blocking enumeration
|
||||||
commsLock sync.Mutex // Lock protecting the pending counter and 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.
|
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
||||||
@ -84,14 +86,20 @@ func NewLedgerHub() (*Hub, error) {
|
|||||||
}, 0xffa0, 0, newLedgerDriver)
|
}, 0xffa0, 0, newLedgerDriver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTrezorHub creates a new hardware wallet manager for Trezor devices.
|
// NewTrezorHubWithHID creates a new hardware wallet manager for Trezor devices.
|
||||||
func NewTrezorHub() (*Hub, error) {
|
func NewTrezorHubWithHID() (*Hub, error) {
|
||||||
return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor 1 */}, 0xff00, 0, newTrezorDriver)
|
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.
|
// 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) {
|
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")
|
return nil, errors.New("unsupported platform")
|
||||||
}
|
}
|
||||||
hub := &Hub{
|
hub := &Hub{
|
||||||
@ -132,8 +140,12 @@ func (hub *Hub) refreshWallets() {
|
|||||||
if elapsed < refreshThrottling {
|
if elapsed < refreshThrottling {
|
||||||
return
|
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
|
// Retrieve the current list of USB wallet devices
|
||||||
var devices []hid.DeviceInfo
|
var devices []usb.DeviceInfo
|
||||||
|
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
// hidapi on Linux opens the device during enumeration to retrieve some infos,
|
// hidapi on Linux opens the device during enumeration to retrieve some infos,
|
||||||
@ -148,8 +160,22 @@ func (hub *Hub) refreshWallets() {
|
|||||||
return
|
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 {
|
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) {
|
if info.ProductID == id && (info.UsagePage == hub.usageID || info.Interface == hub.endpointID) {
|
||||||
devices = append(devices, info)
|
devices = append(devices, info)
|
||||||
break
|
break
|
@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
@ -341,7 +342,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
|
|||||||
op = ledgerP1ContTransactionData
|
op = ledgerP1ContTransactionData
|
||||||
}
|
}
|
||||||
// Extract the Ethereum signature and do a sanity validation
|
// Extract the Ethereum signature and do a sanity validation
|
||||||
if len(reply) != 65 {
|
if len(reply) != crypto.SignatureLength {
|
||||||
return common.Address{}, nil, errors.New("reply lacks signature")
|
return common.Address{}, nil, errors.New("reply lacks signature")
|
||||||
}
|
}
|
||||||
signature := append(reply[1:], reply[0])
|
signature := append(reply[1:], reply[0])
|
365
accounts/usbwallet/trezor.go
Normal file
365
accounts/usbwallet/trezor.go
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
// Copyright 2017 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/>.
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
package usbwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In
|
||||||
|
// this case, the calling application should display a pinpad and send back the
|
||||||
|
// encoded passphrase.
|
||||||
|
var ErrTrezorPINNeeded = errors.New("trezor: pin needed")
|
||||||
|
|
||||||
|
// ErrTrezorPassphraseNeeded is returned if opening the trezor requires a passphrase
|
||||||
|
var ErrTrezorPassphraseNeeded = errors.New("trezor: passphrase needed")
|
||||||
|
|
||||||
|
// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange
|
||||||
|
// if the device replies with a mismatching header. This usually means the device
|
||||||
|
// is in browser mode.
|
||||||
|
var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header")
|
||||||
|
|
||||||
|
// trezorDriver implements the communication with a Trezor hardware wallet.
|
||||||
|
type trezorDriver struct {
|
||||||
|
device io.ReadWriter // USB device connection to communicate through
|
||||||
|
version [3]uint32 // Current version of the Trezor firmware
|
||||||
|
label string // Current textual label of the Trezor device
|
||||||
|
pinwait bool // Flags whether the device is waiting for PIN entry
|
||||||
|
passphrasewait bool // Flags whether the device is waiting for passphrase entry
|
||||||
|
failure error // Any failure that would make the device unusable
|
||||||
|
log log.Logger // Contextual logger to tag the trezor with its id
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTrezorDriver creates a new instance of a Trezor USB protocol driver.
|
||||||
|
func newTrezorDriver(logger log.Logger) driver {
|
||||||
|
return &trezorDriver{
|
||||||
|
log: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status implements accounts.Wallet, always whether the Trezor is opened, closed
|
||||||
|
// or whether the Ethereum app was not started on it.
|
||||||
|
func (w *trezorDriver) Status() (string, error) {
|
||||||
|
if w.failure != nil {
|
||||||
|
return fmt.Sprintf("Failed: %v", w.failure), w.failure
|
||||||
|
}
|
||||||
|
if w.device == nil {
|
||||||
|
return "Closed", w.failure
|
||||||
|
}
|
||||||
|
if w.pinwait {
|
||||||
|
return fmt.Sprintf("Trezor v%d.%d.%d '%s' waiting for PIN", w.version[0], w.version[1], w.version[2], w.label), w.failure
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Trezor v%d.%d.%d '%s' online", w.version[0], w.version[1], w.version[2], w.label), w.failure
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open implements usbwallet.driver, attempting to initialize the connection to
|
||||||
|
// the Trezor hardware wallet. Initializing the Trezor is a two or three phase operation:
|
||||||
|
// * The first phase is to initialize the connection and read the wallet's
|
||||||
|
// features. This phase is invoked if the provided passphrase is empty. The
|
||||||
|
// device will display the pinpad as a result and will return an appropriate
|
||||||
|
// error to notify the user that a second open phase is needed.
|
||||||
|
// * The second phase is to unlock access to the Trezor, which is done by the
|
||||||
|
// user actually providing a passphrase mapping a keyboard keypad to the pin
|
||||||
|
// number of the user (shuffled according to the pinpad displayed).
|
||||||
|
// * If needed the device will ask for passphrase which will require calling
|
||||||
|
// open again with the actual passphrase (3rd phase)
|
||||||
|
func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
|
||||||
|
w.device, w.failure = device, nil
|
||||||
|
|
||||||
|
// If phase 1 is requested, init the connection and wait for user callback
|
||||||
|
if passphrase == "" && !w.passphrasewait {
|
||||||
|
// If we're already waiting for a PIN entry, insta-return
|
||||||
|
if w.pinwait {
|
||||||
|
return ErrTrezorPINNeeded
|
||||||
|
}
|
||||||
|
// Initialize a connection to the device
|
||||||
|
features := new(trezor.Features)
|
||||||
|
if _, err := w.trezorExchange(&trezor.Initialize{}, features); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()}
|
||||||
|
w.label = features.GetLabel()
|
||||||
|
|
||||||
|
// Do a manual ping, forcing the device to ask for its PIN and Passphrase
|
||||||
|
askPin := true
|
||||||
|
askPassphrase := true
|
||||||
|
res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin, PassphraseProtection: &askPassphrase}, new(trezor.PinMatrixRequest), new(trezor.PassphraseRequest), new(trezor.Success))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Only return the PIN request if the device wasn't unlocked until now
|
||||||
|
switch res {
|
||||||
|
case 0:
|
||||||
|
w.pinwait = true
|
||||||
|
return ErrTrezorPINNeeded
|
||||||
|
case 1:
|
||||||
|
w.pinwait = false
|
||||||
|
w.passphrasewait = true
|
||||||
|
return ErrTrezorPassphraseNeeded
|
||||||
|
case 2:
|
||||||
|
return nil // responded with trezor.Success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Phase 2 requested with actual PIN entry
|
||||||
|
if w.pinwait {
|
||||||
|
w.pinwait = false
|
||||||
|
res, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success), new(trezor.PassphraseRequest))
|
||||||
|
if err != nil {
|
||||||
|
w.failure = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if res == 1 {
|
||||||
|
w.passphrasewait = true
|
||||||
|
return ErrTrezorPassphraseNeeded
|
||||||
|
}
|
||||||
|
} else if w.passphrasewait {
|
||||||
|
w.passphrasewait = false
|
||||||
|
if _, err := w.trezorExchange(&trezor.PassphraseAck{Passphrase: &passphrase}, new(trezor.Success)); err != nil {
|
||||||
|
w.failure = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements usbwallet.driver, cleaning up and metadata maintained within
|
||||||
|
// the Trezor driver.
|
||||||
|
func (w *trezorDriver) Close() error {
|
||||||
|
w.version, w.label, w.pinwait = [3]uint32{}, "", false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heartbeat implements usbwallet.driver, performing a sanity check against the
|
||||||
|
// Trezor to see if it's still online.
|
||||||
|
func (w *trezorDriver) Heartbeat() error {
|
||||||
|
if _, err := w.trezorExchange(&trezor.Ping{}, new(trezor.Success)); err != nil {
|
||||||
|
w.failure = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive implements usbwallet.driver, sending a derivation request to the Trezor
|
||||||
|
// and returning the Ethereum address located on that derivation path.
|
||||||
|
func (w *trezorDriver) Derive(path accounts.DerivationPath) (common.Address, error) {
|
||||||
|
return w.trezorDerive(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTx implements usbwallet.driver, sending the transaction to the Trezor and
|
||||||
|
// waiting for the user to confirm or deny the transaction.
|
||||||
|
func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
|
||||||
|
if w.device == nil {
|
||||||
|
return common.Address{}, nil, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
return w.trezorSign(path, tx, chainID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// trezorDerive sends a derivation request to the Trezor device and returns the
|
||||||
|
// Ethereum address located on that path.
|
||||||
|
func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) {
|
||||||
|
address := new(trezor.EthereumAddress)
|
||||||
|
if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
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
|
||||||
|
// to confirm or deny the transaction.
|
||||||
|
func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
|
||||||
|
// Create the transaction initiation message
|
||||||
|
data := tx.Data()
|
||||||
|
length := uint32(len(data))
|
||||||
|
|
||||||
|
request := &trezor.EthereumSignTx{
|
||||||
|
AddressN: derivationPath,
|
||||||
|
Nonce: new(big.Int).SetUint64(tx.Nonce()).Bytes(),
|
||||||
|
GasPrice: tx.GasPrice().Bytes(),
|
||||||
|
GasLimit: new(big.Int).SetUint64(tx.Gas()).Bytes(),
|
||||||
|
Value: tx.Value().Bytes(),
|
||||||
|
DataLength: &length,
|
||||||
|
}
|
||||||
|
if to := tx.To(); to != nil {
|
||||||
|
// 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:]
|
||||||
|
} else {
|
||||||
|
request.DataInitialChunk, data = data, nil
|
||||||
|
}
|
||||||
|
if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?)
|
||||||
|
id := uint32(chainID.Int64())
|
||||||
|
request.ChainId = &id
|
||||||
|
}
|
||||||
|
// Send the initiation message and stream content until a signature is returned
|
||||||
|
response := new(trezor.EthereumTxRequest)
|
||||||
|
if _, err := w.trezorExchange(request, response); err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
for response.DataLength != nil && int(*response.DataLength) <= len(data) {
|
||||||
|
chunk := data[:*response.DataLength]
|
||||||
|
data = data[*response.DataLength:]
|
||||||
|
|
||||||
|
if _, err := w.trezorExchange(&trezor.EthereumTxAck{DataChunk: chunk}, response); err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Extract the Ethereum signature and do a sanity validation
|
||||||
|
if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 || response.GetSignatureV() == 0 {
|
||||||
|
return common.Address{}, nil, errors.New("reply lacks signature")
|
||||||
|
}
|
||||||
|
signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV()))
|
||||||
|
|
||||||
|
// Create the correct signer and signature transform based on the chain ID
|
||||||
|
var signer types.Signer
|
||||||
|
if chainID == nil {
|
||||||
|
signer = new(types.HomesteadSigner)
|
||||||
|
} else {
|
||||||
|
signer = types.NewEIP155Signer(chainID)
|
||||||
|
signature[64] -= byte(chainID.Uint64()*2 + 35)
|
||||||
|
}
|
||||||
|
// Inject the final signature into the transaction and sanity check the sender
|
||||||
|
signed, err := tx.WithSignature(signer, signature)
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
sender, err := types.Sender(signer, signed)
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
return sender, signed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// trezorExchange performs a data exchange with the Trezor wallet, sending it a
|
||||||
|
// message and retrieving the response. If multiple responses are possible, the
|
||||||
|
// method will also return the index of the destination object used.
|
||||||
|
func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Message) (int, error) {
|
||||||
|
// Construct the original message payload to chunk up
|
||||||
|
data, err := proto.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
payload := make([]byte, 8+len(data))
|
||||||
|
copy(payload, []byte{0x23, 0x23})
|
||||||
|
binary.BigEndian.PutUint16(payload[2:], trezor.Type(req))
|
||||||
|
binary.BigEndian.PutUint32(payload[4:], uint32(len(data)))
|
||||||
|
copy(payload[8:], data)
|
||||||
|
|
||||||
|
// Stream all the chunks to the device
|
||||||
|
chunk := make([]byte, 64)
|
||||||
|
chunk[0] = 0x3f // Report ID magic number
|
||||||
|
|
||||||
|
for len(payload) > 0 {
|
||||||
|
// Construct the new message to stream, padding with zeroes if needed
|
||||||
|
if len(payload) > 63 {
|
||||||
|
copy(chunk[1:], payload[:63])
|
||||||
|
payload = payload[63:]
|
||||||
|
} else {
|
||||||
|
copy(chunk[1:], payload)
|
||||||
|
copy(chunk[1+len(payload):], make([]byte, 63-len(payload)))
|
||||||
|
payload = nil
|
||||||
|
}
|
||||||
|
// Send over to the device
|
||||||
|
w.log.Trace("Data chunk sent to the Trezor", "chunk", hexutil.Bytes(chunk))
|
||||||
|
if _, err := w.device.Write(chunk); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Stream the reply back from the wallet in 64 byte chunks
|
||||||
|
var (
|
||||||
|
kind uint16
|
||||||
|
reply []byte
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
// Read the next chunk from the Trezor wallet
|
||||||
|
if _, err := io.ReadFull(w.device, chunk); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
w.log.Trace("Data chunk received from the Trezor", "chunk", hexutil.Bytes(chunk))
|
||||||
|
|
||||||
|
// Make sure the transport header matches
|
||||||
|
if chunk[0] != 0x3f || (len(reply) == 0 && (chunk[1] != 0x23 || chunk[2] != 0x23)) {
|
||||||
|
return 0, errTrezorReplyInvalidHeader
|
||||||
|
}
|
||||||
|
// If it's the first chunk, retrieve the reply message type and total message length
|
||||||
|
var payload []byte
|
||||||
|
|
||||||
|
if len(reply) == 0 {
|
||||||
|
kind = binary.BigEndian.Uint16(chunk[3:5])
|
||||||
|
reply = make([]byte, 0, int(binary.BigEndian.Uint32(chunk[5:9])))
|
||||||
|
payload = chunk[9:]
|
||||||
|
} else {
|
||||||
|
payload = chunk[1:]
|
||||||
|
}
|
||||||
|
// Append to the reply and stop when filled up
|
||||||
|
if left := cap(reply) - len(reply); left > len(payload) {
|
||||||
|
reply = append(reply, payload...)
|
||||||
|
} else {
|
||||||
|
reply = append(reply, payload[:left]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try to parse the reply into the requested reply message
|
||||||
|
if kind == uint16(trezor.MessageType_MessageType_Failure) {
|
||||||
|
// Trezor returned a failure, extract and return the message
|
||||||
|
failure := new(trezor.Failure)
|
||||||
|
if err := proto.Unmarshal(reply, failure); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return 0, errors.New("trezor: " + failure.GetMessage())
|
||||||
|
}
|
||||||
|
if kind == uint16(trezor.MessageType_MessageType_ButtonRequest) {
|
||||||
|
// Trezor is waiting for user confirmation, ack and wait for the next message
|
||||||
|
return w.trezorExchange(&trezor.ButtonAck{}, results...)
|
||||||
|
}
|
||||||
|
for i, res := range results {
|
||||||
|
if trezor.Type(res) == kind {
|
||||||
|
return i, proto.Unmarshal(reply, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expected := make([]string, len(results))
|
||||||
|
for i, res := range results {
|
||||||
|
expected[i] = trezor.Name(trezor.Type(res))
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("trezor: expected reply types %s, got %s", expected, trezor.Name(kind))
|
||||||
|
}
|
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,
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user