Merge branch 'goeth-1.8.18' into geth-upgrade-1.8.18
# Conflicts: # .github/CONTRIBUTING.md # .travis.yml # core/blockchain.go # core/vm/evm.go # core/vm/logger_test.go # eth/handler_test.go # eth/tracers/tracer_test.go # internal/ethapi/api.go # internal/web3ext/web3ext.go # p2p/discover/node.go # p2p/server.go # swarm/network/simulations/discovery/discovery_test.go
|
@ -9,24 +9,27 @@ les/ @zsfelfoldi
|
||||||
light/ @zsfelfoldi
|
light/ @zsfelfoldi
|
||||||
mobile/ @karalabe
|
mobile/ @karalabe
|
||||||
p2p/ @fjl @zsfelfoldi
|
p2p/ @fjl @zsfelfoldi
|
||||||
|
p2p/simulations @lmars
|
||||||
|
p2p/protocols @zelig
|
||||||
|
swarm/api/http @justelad
|
||||||
swarm/bmt @zelig
|
swarm/bmt @zelig
|
||||||
swarm/dev @lmars
|
swarm/dev @lmars
|
||||||
swarm/fuse @jmozah @holisticode
|
swarm/fuse @jmozah @holisticode
|
||||||
swarm/grafana_dashboards @nonsense
|
swarm/grafana_dashboards @nonsense
|
||||||
swarm/metrics @nonsense @holisticode
|
swarm/metrics @nonsense @holisticode
|
||||||
swarm/multihash @nolash
|
swarm/multihash @nolash
|
||||||
swarm/network/bitvector @zelig @janos @gbalint
|
swarm/network/bitvector @zelig @janos
|
||||||
swarm/network/priorityqueue @zelig @janos @gbalint
|
swarm/network/priorityqueue @zelig @janos
|
||||||
swarm/network/simulations @zelig
|
swarm/network/simulations @zelig @janos
|
||||||
swarm/network/stream @janos @zelig @gbalint @holisticode @justelad
|
swarm/network/stream @janos @zelig @holisticode @justelad
|
||||||
swarm/network/stream/intervals @janos
|
swarm/network/stream/intervals @janos
|
||||||
swarm/network/stream/testing @zelig
|
swarm/network/stream/testing @zelig
|
||||||
swarm/pot @zelig
|
swarm/pot @zelig
|
||||||
swarm/pss @nolash @zelig @nonsense
|
swarm/pss @nolash @zelig @nonsense
|
||||||
swarm/services @zelig
|
swarm/services @zelig
|
||||||
swarm/state @justelad
|
swarm/state @justelad
|
||||||
swarm/storage/encryption @gbalint @zelig @nagydani
|
swarm/storage/encryption @zelig @nagydani
|
||||||
swarm/storage/mock @janos
|
swarm/storage/mock @janos
|
||||||
swarm/storage/mru @nolash
|
swarm/storage/feed @nolash @jpeletier
|
||||||
swarm/testutil @lmars
|
swarm/testutil @lmars
|
||||||
whisper/ @gballet @gluk256
|
whisper/ @gballet @gluk256
|
||||||
|
|
3
Makefile
|
@ -61,6 +61,9 @@ devtools:
|
||||||
@type "solc" 2> /dev/null || echo 'Please install solc'
|
@type "solc" 2> /dev/null || echo 'Please install solc'
|
||||||
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
@type "protoc" 2> /dev/null || echo 'Please install protoc'
|
||||||
|
|
||||||
|
swarm-devtools:
|
||||||
|
env GOBIN= go install ./cmd/swarm/mimegen
|
||||||
|
|
||||||
# Cross Compilation Targets (xgo)
|
# Cross Compilation Targets (xgo)
|
||||||
|
|
||||||
geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios
|
geth-cross: geth-linux geth-darwin geth-windows geth-android geth-ios
|
||||||
|
|
|
@ -137,6 +137,9 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
// MethodById looks up a method by the 4-byte id
|
// MethodById looks up a method by the 4-byte id
|
||||||
// returns nil if none found
|
// returns nil if none found
|
||||||
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
|
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
|
||||||
|
if len(sigdata) < 4 {
|
||||||
|
return nil, fmt.Errorf("data too short (% bytes) for abi method lookup", len(sigdata))
|
||||||
|
}
|
||||||
for _, method := range abi.Methods {
|
for _, method := range abi.Methods {
|
||||||
if bytes.Equal(method.Id(), sigdata[:4]) {
|
if bytes.Equal(method.Id(), sigdata[:4]) {
|
||||||
return &method, nil
|
return &method, nil
|
||||||
|
|
|
@ -711,5 +711,14 @@ func TestABI_MethodById(t *testing.T) {
|
||||||
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
|
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Also test empty
|
||||||
|
if _, err := abi.MethodById([]byte{0x00}); err == nil {
|
||||||
|
t.Errorf("Expected error, too short to decode data")
|
||||||
|
}
|
||||||
|
if _, err := abi.MethodById([]byte{}); err == nil {
|
||||||
|
t.Errorf("Expected error, too short to decode data")
|
||||||
|
}
|
||||||
|
if _, err := abi.MethodById(nil); err == nil {
|
||||||
|
t.Errorf("Expected error, nil is short to decode data")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,7 +208,7 @@ func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Ad
|
||||||
}
|
}
|
||||||
|
|
||||||
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
|
||||||
// chain doens't have miners, we just return a gas price of 1 for any call.
|
// chain doesn't have miners, we just return a gas price of 1 for any call.
|
||||||
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
|
||||||
return big.NewInt(1), nil
|
return big.NewInt(1), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,13 +23,13 @@ package bind
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/format"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"golang.org/x/tools/imports"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Lang is a target programming language selector to generate bindings for.
|
// Lang is a target programming language selector to generate bindings for.
|
||||||
|
@ -145,9 +145,9 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
|
||||||
if err := tmpl.Execute(buffer, data); err != nil {
|
if err := tmpl.Execute(buffer, data); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
// For Go bindings pass the code through goimports to clean it up and double check
|
// For Go bindings pass the code through gofmt to clean it up
|
||||||
if lang == LangGo {
|
if lang == LangGo {
|
||||||
code, err := imports.Process(".", buffer.Bytes(), nil)
|
code, err := format.Source(buffer.Bytes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("%v\n%s", err, buffer)
|
return "", fmt.Errorf("%v\n%s", err, buffer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,30 @@ const tmplSourceGo = `
|
||||||
|
|
||||||
package {{.Package}}
|
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
|
||||||
|
)
|
||||||
|
|
||||||
{{range $contract := .Contracts}}
|
{{range $contract := .Contracts}}
|
||||||
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
// {{.Type}}ABI is the input ABI used to generate the binding from.
|
||||||
const {{.Type}}ABI = "{{.InputABI}}"
|
const {{.Type}}ABI = "{{.InputABI}}"
|
||||||
|
|
|
@ -30,8 +30,8 @@ import (
|
||||||
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
||||||
|
|
||||||
// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
|
// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
|
||||||
// are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
|
// are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second
|
||||||
// at m/44'/60'/0'/1, etc.
|
// at m/44'/60'/0'/0/1, etc.
|
||||||
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
|
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
|
||||||
|
|
||||||
// DefaultLedgerBaseDerivationPath is the base path from which custom derivation endpoints
|
// DefaultLedgerBaseDerivationPath is the base path from which custom derivation endpoints
|
||||||
|
|
|
@ -66,19 +66,19 @@ type plainKeyJSON struct {
|
||||||
|
|
||||||
type encryptedKeyJSONV3 struct {
|
type encryptedKeyJSONV3 struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Crypto cryptoJSON `json:"crypto"`
|
Crypto CryptoJSON `json:"crypto"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type encryptedKeyJSONV1 struct {
|
type encryptedKeyJSONV1 struct {
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Crypto cryptoJSON `json:"crypto"`
|
Crypto CryptoJSON `json:"crypto"`
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type cryptoJSON struct {
|
type CryptoJSON struct {
|
||||||
Cipher string `json:"cipher"`
|
Cipher string `json:"cipher"`
|
||||||
CipherText string `json:"ciphertext"`
|
CipherText string `json:"ciphertext"`
|
||||||
CipherParams cipherparamsJSON `json:"cipherparams"`
|
CipherParams cipherparamsJSON `json:"cipherparams"`
|
||||||
|
|
|
@ -135,29 +135,26 @@ func (ks keyStorePassphrase) JoinPath(filename string) string {
|
||||||
return filepath.Join(ks.keysDirPath, filename)
|
return filepath.Join(ks.keysDirPath, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncryptKey encrypts a key using the specified scrypt parameters into a json
|
// Encryptdata encrypts the data given as 'data' with the password 'auth'.
|
||||||
// blob that can be decrypted later on.
|
func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
|
||||||
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
|
||||||
authArray := []byte(auth)
|
|
||||||
|
|
||||||
salt := make([]byte, 32)
|
salt := make([]byte, 32)
|
||||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||||
panic("reading from crypto/rand failed: " + err.Error())
|
panic("reading from crypto/rand failed: " + err.Error())
|
||||||
}
|
}
|
||||||
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen)
|
derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return CryptoJSON{}, err
|
||||||
}
|
}
|
||||||
encryptKey := derivedKey[:16]
|
encryptKey := derivedKey[:16]
|
||||||
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
|
|
||||||
|
|
||||||
iv := make([]byte, aes.BlockSize) // 16
|
iv := make([]byte, aes.BlockSize) // 16
|
||||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
panic("reading from crypto/rand failed: " + err.Error())
|
panic("reading from crypto/rand failed: " + err.Error())
|
||||||
}
|
}
|
||||||
cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv)
|
cipherText, err := aesCTRXOR(encryptKey, data, iv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return CryptoJSON{}, err
|
||||||
}
|
}
|
||||||
mac := crypto.Keccak256(derivedKey[16:32], cipherText)
|
mac := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||||
|
|
||||||
|
@ -167,12 +164,11 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||||
scryptParamsJSON["p"] = scryptP
|
scryptParamsJSON["p"] = scryptP
|
||||||
scryptParamsJSON["dklen"] = scryptDKLen
|
scryptParamsJSON["dklen"] = scryptDKLen
|
||||||
scryptParamsJSON["salt"] = hex.EncodeToString(salt)
|
scryptParamsJSON["salt"] = hex.EncodeToString(salt)
|
||||||
|
|
||||||
cipherParamsJSON := cipherparamsJSON{
|
cipherParamsJSON := cipherparamsJSON{
|
||||||
IV: hex.EncodeToString(iv),
|
IV: hex.EncodeToString(iv),
|
||||||
}
|
}
|
||||||
|
|
||||||
cryptoStruct := cryptoJSON{
|
cryptoStruct := CryptoJSON{
|
||||||
Cipher: "aes-128-ctr",
|
Cipher: "aes-128-ctr",
|
||||||
CipherText: hex.EncodeToString(cipherText),
|
CipherText: hex.EncodeToString(cipherText),
|
||||||
CipherParams: cipherParamsJSON,
|
CipherParams: cipherParamsJSON,
|
||||||
|
@ -180,6 +176,17 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||||
KDFParams: scryptParamsJSON,
|
KDFParams: scryptParamsJSON,
|
||||||
MAC: hex.EncodeToString(mac),
|
MAC: hex.EncodeToString(mac),
|
||||||
}
|
}
|
||||||
|
return cryptoStruct, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptKey encrypts a key using the specified scrypt parameters into a json
|
||||||
|
// blob that can be decrypted later on.
|
||||||
|
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
|
||||||
|
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
|
||||||
|
cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
encryptedKeyJSONV3 := encryptedKeyJSONV3{
|
encryptedKeyJSONV3 := encryptedKeyJSONV3{
|
||||||
hex.EncodeToString(key.Address[:]),
|
hex.EncodeToString(key.Address[:]),
|
||||||
cryptoStruct,
|
cryptoStruct,
|
||||||
|
@ -226,43 +233,48 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) {
|
||||||
PrivateKey: key,
|
PrivateKey: key,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
|
||||||
|
if cryptoJson.Cipher != "aes-128-ctr" {
|
||||||
|
return nil, fmt.Errorf("Cipher not supported: %v", cryptoJson.Cipher)
|
||||||
|
}
|
||||||
|
mac, err := hex.DecodeString(cryptoJson.MAC)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherText, err := hex.DecodeString(cryptoJson.CipherText)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
derivedKey, err := getKDFKey(cryptoJson, auth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
||||||
|
if !bytes.Equal(calculatedMAC, mac) {
|
||||||
|
return nil, ErrDecrypt
|
||||||
|
}
|
||||||
|
|
||||||
|
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return plainText, err
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
if keyProtected.Crypto.Cipher != "aes-128-ctr" {
|
|
||||||
return nil, nil, fmt.Errorf("Cipher not supported: %v", keyProtected.Crypto.Cipher)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyId = uuid.Parse(keyProtected.Id)
|
keyId = uuid.Parse(keyProtected.Id)
|
||||||
mac, err := hex.DecodeString(keyProtected.Crypto.MAC)
|
plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
|
||||||
if !bytes.Equal(calculatedMAC, mac) {
|
|
||||||
return nil, nil, ErrDecrypt
|
|
||||||
}
|
|
||||||
|
|
||||||
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -303,7 +315,7 @@ func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byt
|
||||||
return plainText, keyId, err
|
return plainText, keyId, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) {
|
func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
|
||||||
authArray := []byte(auth)
|
authArray := []byte(auth)
|
||||||
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
|
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -350,7 +350,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
|
||||||
signer = new(types.HomesteadSigner)
|
signer = new(types.HomesteadSigner)
|
||||||
} else {
|
} else {
|
||||||
signer = types.NewEIP155Signer(chainID)
|
signer = types.NewEIP155Signer(chainID)
|
||||||
signature[64] = signature[64] - byte(chainID.Uint64()*2+35)
|
signature[64] -= byte(chainID.Uint64()*2 + 35)
|
||||||
}
|
}
|
||||||
signed, err := tx.WithSignature(signer, signature)
|
signed, err := tx.WithSignature(signer, signature)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -221,7 +221,7 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
|
||||||
signer = new(types.HomesteadSigner)
|
signer = new(types.HomesteadSigner)
|
||||||
} else {
|
} else {
|
||||||
signer = types.NewEIP155Signer(chainID)
|
signer = types.NewEIP155Signer(chainID)
|
||||||
signature[64] = signature[64] - byte(chainID.Uint64()*2+35)
|
signature[64] -= byte(chainID.Uint64()*2 + 35)
|
||||||
}
|
}
|
||||||
// Inject the final signature into the transaction and sanity check the sender
|
// Inject the final signature into the transaction and sanity check the sender
|
||||||
signed, err := tx.WithSignature(signer, signature)
|
signed, err := tx.WithSignature(signer, signature)
|
||||||
|
|
|
@ -23,8 +23,8 @@ environment:
|
||||||
install:
|
install:
|
||||||
- git submodule update --init
|
- git submodule update --init
|
||||||
- rmdir C:\go /s /q
|
- rmdir C:\go /s /q
|
||||||
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.11.windows-%GETH_ARCH%.zip
|
- appveyor DownloadFile https://storage.googleapis.com/golang/go1.11.2.windows-%GETH_ARCH%.zip
|
||||||
- 7z x go1.11.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
- 7z x go1.11.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL
|
||||||
- go version
|
- go version
|
||||||
- gcc --version
|
- gcc --version
|
||||||
|
|
||||||
|
|
13
build/ci.go
|
@ -320,9 +320,7 @@ func goToolArch(arch string, cc string, subcmd string, args ...string) *exec.Cmd
|
||||||
// "tests" also includes static analysis tools such as vet.
|
// "tests" also includes static analysis tools such as vet.
|
||||||
|
|
||||||
func doTest(cmdline []string) {
|
func doTest(cmdline []string) {
|
||||||
var (
|
coverage := flag.Bool("coverage", false, "Whether to record code coverage")
|
||||||
coverage = flag.Bool("coverage", false, "Whether to record code coverage")
|
|
||||||
)
|
|
||||||
flag.CommandLine.Parse(cmdline)
|
flag.CommandLine.Parse(cmdline)
|
||||||
env := build.Env()
|
env := build.Env()
|
||||||
|
|
||||||
|
@ -332,14 +330,11 @@ func doTest(cmdline []string) {
|
||||||
}
|
}
|
||||||
packages = build.ExpandPackagesNoVendor(packages)
|
packages = build.ExpandPackagesNoVendor(packages)
|
||||||
|
|
||||||
// Run analysis tools before the tests.
|
|
||||||
build.MustRun(goTool("vet", packages...))
|
|
||||||
|
|
||||||
// Run the actual tests.
|
// Run the actual tests.
|
||||||
gotest := goTool("test", buildFlags(env)...)
|
|
||||||
// Test a single package at a time. CI builders are slow
|
// Test a single package at a time. CI builders are slow
|
||||||
// and some tests run into timeouts under load.
|
// and some tests run into timeouts under load.
|
||||||
gotest.Args = append(gotest.Args, "-p", "1")
|
gotest := goTool("test", buildFlags(env)...)
|
||||||
|
gotest.Args = append(gotest.Args, "-p", "1", "-timeout", "5m")
|
||||||
if *coverage {
|
if *coverage {
|
||||||
gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
|
gotest.Args = append(gotest.Args, "-covermode=atomic", "-cover")
|
||||||
}
|
}
|
||||||
|
@ -1040,7 +1035,7 @@ func xgoTool(args []string) *exec.Cmd {
|
||||||
func doPurge(cmdline []string) {
|
func doPurge(cmdline []string) {
|
||||||
var (
|
var (
|
||||||
store = flag.String("store", "", `Destination from where to purge archives (usually "gethstore/builds")`)
|
store = flag.String("store", "", `Destination from where to purge archives (usually "gethstore/builds")`)
|
||||||
limit = flag.Int("days", 30, `Age threshold above which to delete unstalbe archives`)
|
limit = flag.Int("days", 30, `Age threshold above which to delete unstable archives`)
|
||||||
)
|
)
|
||||||
flag.CommandLine.Parse(cmdline)
|
flag.CommandLine.Parse(cmdline)
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ func main() {
|
||||||
bins []string
|
bins []string
|
||||||
types []string
|
types []string
|
||||||
)
|
)
|
||||||
if *solFlag != "" || *abiFlag == "-" {
|
if *solFlag != "" || (*abiFlag == "-" && *pkgFlag == "") {
|
||||||
// Generate the list of types to exclude from binding
|
// Generate the list of types to exclude from binding
|
||||||
exclude := make(map[string]bool)
|
exclude := make(map[string]bool)
|
||||||
for _, kind := range strings.Split(*excFlag, ",") {
|
for _, kind := range strings.Split(*excFlag, ",") {
|
||||||
|
@ -111,7 +111,13 @@ func main() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise load up the ABI, optional bytecode and type name from the parameters
|
// Otherwise load up the ABI, optional bytecode and type name from the parameters
|
||||||
abi, err := ioutil.ReadFile(*abiFlag)
|
var abi []byte
|
||||||
|
var err error
|
||||||
|
if *abiFlag == "-" {
|
||||||
|
abi, err = ioutil.ReadAll(os.Stdin)
|
||||||
|
} else {
|
||||||
|
abi, err = ioutil.ReadFile(*abiFlag)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to read input ABI: %v\n", err)
|
fmt.Printf("Failed to read input ABI: %v\n", err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
|
@ -155,6 +161,5 @@ func contractsFromStdin() (map[string]*compiler.Contract, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return compiler.ParseCombinedJSON(bytes, "", "", "", "")
|
return compiler.ParseCombinedJSON(bytes, "", "", "", "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||||
)
|
)
|
||||||
|
@ -37,7 +38,7 @@ func main() {
|
||||||
var (
|
var (
|
||||||
listenAddr = flag.String("addr", ":30301", "listen address")
|
listenAddr = flag.String("addr", ":30301", "listen address")
|
||||||
genKey = flag.String("genkey", "", "generate a node key")
|
genKey = flag.String("genkey", "", "generate a node key")
|
||||||
writeAddr = flag.Bool("writeaddress", false, "write out the node's pubkey hash and quit")
|
writeAddr = flag.Bool("writeaddress", false, "write out the node's public key and quit")
|
||||||
nodeKeyFile = flag.String("nodekey", "", "private key filename")
|
nodeKeyFile = flag.String("nodekey", "", "private key filename")
|
||||||
nodeKeyHex = flag.String("nodekeyhex", "", "private key as hex (for testing)")
|
nodeKeyHex = flag.String("nodekeyhex", "", "private key as hex (for testing)")
|
||||||
natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|extip:<IP>)")
|
natdesc = flag.String("nat", "none", "port mapping mechanism (any|none|upnp|pmp|extip:<IP>)")
|
||||||
|
@ -85,7 +86,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if *writeAddr {
|
if *writeAddr {
|
||||||
fmt.Printf("%v\n", discover.PubkeyID(&nodeKey.PublicKey))
|
fmt.Printf("%x\n", crypto.FromECDSAPub(&nodeKey.PublicKey)[1:])
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,16 +119,17 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if *runv5 {
|
if *runv5 {
|
||||||
if _, err := discv5.ListenUDP(nodeKey, conn, realaddr, "", restrictList); err != nil {
|
if _, err := discv5.ListenUDP(nodeKey, conn, "", restrictList); err != nil {
|
||||||
utils.Fatalf("%v", err)
|
utils.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
db, _ := enode.OpenDB("")
|
||||||
|
ln := enode.NewLocalNode(db, nodeKey)
|
||||||
cfg := discover.Config{
|
cfg := discover.Config{
|
||||||
PrivateKey: nodeKey,
|
PrivateKey: nodeKey,
|
||||||
AnnounceAddr: realaddr,
|
|
||||||
NetRestrict: restrictList,
|
NetRestrict: restrictList,
|
||||||
}
|
}
|
||||||
if _, err := discover.ListenUDP(conn, cfg); err != nil {
|
if _, err := discover.ListenUDP(conn, ln, cfg); err != nil {
|
||||||
utils.Fatalf("%v", err)
|
utils.Fatalf("%v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ invoking methods with the following info:
|
||||||
* [x] Version info about the signer
|
* [x] Version info about the signer
|
||||||
* [x] Address of API (http/ipc)
|
* [x] Address of API (http/ipc)
|
||||||
* [ ] List of known accounts
|
* [ ] List of known accounts
|
||||||
* [ ] Have a default timeout on signing operations, so that if the user has not answered withing e.g. 60 seconds, the request is rejected.
|
* [ ] Have a default timeout on signing operations, so that if the user has not answered within e.g. 60 seconds, the request is rejected.
|
||||||
* [ ] `account_signRawTransaction`
|
* [ ] `account_signRawTransaction`
|
||||||
* [ ] `account_bulkSignTransactions([] transactions)` should
|
* [ ] `account_bulkSignTransactions([] transactions)` should
|
||||||
* only exist if enabled via config/flag
|
* only exist if enabled via config/flag
|
||||||
|
@ -129,7 +129,7 @@ The signer listens to HTTP requests on `rpcaddr`:`rpcport`, with the same JSONRP
|
||||||
expected to be JSON [jsonrpc 2.0 standard](http://www.jsonrpc.org/specification).
|
expected to be JSON [jsonrpc 2.0 standard](http://www.jsonrpc.org/specification).
|
||||||
|
|
||||||
Some of these call can require user interaction. Clients must be aware that responses
|
Some of these call can require user interaction. Clients must be aware that responses
|
||||||
may be delayed significanlty or may never be received if a users decides to ignore the confirmation request.
|
may be delayed significantly or may never be received if a users decides to ignore the confirmation request.
|
||||||
|
|
||||||
The External API is **untrusted** : it does not accept credentials over this api, nor does it expect
|
The External API is **untrusted** : it does not accept credentials over this api, nor does it expect
|
||||||
that requests have any authority.
|
that requests have any authority.
|
||||||
|
@ -862,7 +862,7 @@ A UI should conform to the following rules.
|
||||||
* A UI SHOULD inform the user about the `SHA256` or `MD5` hash of the binary being executed
|
* A UI SHOULD inform the user about the `SHA256` or `MD5` hash of the binary being executed
|
||||||
* A UI SHOULD NOT maintain a secondary storage of data, e.g. list of accounts
|
* A UI SHOULD NOT maintain a secondary storage of data, e.g. list of accounts
|
||||||
* The signer provides accounts
|
* The signer provides accounts
|
||||||
* A UI SHOULD, to the best extent possible, use static linking / bundling, so that requried libraries are bundled
|
* A UI SHOULD, to the best extent possible, use static linking / bundling, so that required libraries are bundled
|
||||||
along with the UI.
|
along with the UI.
|
||||||
|
|
||||||
|
|
||||||
|
@ -875,3 +875,4 @@ There are a couple of implementation for a UI. We'll try to keep this list up to
|
||||||
| QtSigner| https://github.com/holiman/qtsigner/| Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)|
|
| QtSigner| https://github.com/holiman/qtsigner/| Python3/QT-based| :+1:| :+1:| :+1:| :+1:| :+1:| :x: | :+1: (partially)|
|
||||||
| GtkSigner| https://github.com/holiman/gtksigner| Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: |
|
| GtkSigner| https://github.com/holiman/gtksigner| Python3/GTK-based| :+1:| :x:| :x:| :+1:| :+1:| :x: | :x: |
|
||||||
| Frame | https://github.com/floating/frame/commits/go-signer| Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: |
|
| Frame | https://github.com/floating/frame/commits/go-signer| Electron-based| :x:| :x:| :x:| :x:| ?| :x: | :x: |
|
||||||
|
| Clef UI| https://github.com/kyokan/clef-ui| Golang/QT-based| :+1:| :+1:| :x:| :+1:| :+1:| :x: | :+1: (approve tx only)|
|
||||||
|
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 36 KiB |
|
@ -1,6 +1,13 @@
|
||||||
### Changelog for external API
|
### Changelog for external API
|
||||||
|
|
||||||
|
#### 4.0.0
|
||||||
|
|
||||||
|
* The external `account_Ecrecover`-method was removed.
|
||||||
|
* The external `account_Import`-method was removed.
|
||||||
|
|
||||||
|
#### 3.0.0
|
||||||
|
|
||||||
|
* The external `account_List`-method was changed to not expose `url`, which contained info about the local filesystem. It now returns only a list of addresses.
|
||||||
|
|
||||||
#### 2.0.0
|
#### 2.0.0
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,24 @@
|
||||||
### Changelog for internal API (ui-api)
|
### Changelog for internal API (ui-api)
|
||||||
|
|
||||||
|
### 3.0.0
|
||||||
|
|
||||||
|
* Make use of `OnInputRequired(info UserInputRequest)` for obtaining master password during startup
|
||||||
|
|
||||||
|
### 2.1.0
|
||||||
|
|
||||||
|
* Add `OnInputRequired(info UserInputRequest)` to internal API. This method is used when Clef needs user input, e.g. passwords.
|
||||||
|
|
||||||
|
The following structures are used:
|
||||||
|
```golang
|
||||||
|
UserInputRequest struct {
|
||||||
|
Prompt string `json:"prompt"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
IsPassword bool `json:"isPassword"`
|
||||||
|
}
|
||||||
|
UserInputResponse struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
|
||||||
### 2.0.0
|
### 2.0.0
|
||||||
|
|
||||||
* Modify how `call_info` on a transaction is conveyed. New format:
|
* Modify how `call_info` on a transaction is conveyed. New format:
|
||||||
|
|
215
cmd/clef/main.go
|
@ -35,8 +35,10 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/console"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
|
@ -48,10 +50,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExternalAPIVersion -- see extapi_changelog.md
|
// ExternalAPIVersion -- see extapi_changelog.md
|
||||||
const ExternalAPIVersion = "2.0.0"
|
const ExternalAPIVersion = "4.0.0"
|
||||||
|
|
||||||
// InternalAPIVersion -- see intapi_changelog.md
|
// InternalAPIVersion -- see intapi_changelog.md
|
||||||
const InternalAPIVersion = "2.0.0"
|
const InternalAPIVersion = "3.0.0"
|
||||||
|
|
||||||
const legalWarning = `
|
const legalWarning = `
|
||||||
WARNING!
|
WARNING!
|
||||||
|
@ -70,6 +72,10 @@ var (
|
||||||
Value: 4,
|
Value: 4,
|
||||||
Usage: "log level to emit to the screen",
|
Usage: "log level to emit to the screen",
|
||||||
}
|
}
|
||||||
|
advancedMode = cli.BoolFlag{
|
||||||
|
Name: "advanced",
|
||||||
|
Usage: "If enabled, issues warnings instead of rejections for suspicious requests. Default off",
|
||||||
|
}
|
||||||
keystoreFlag = cli.StringFlag{
|
keystoreFlag = cli.StringFlag{
|
||||||
Name: "keystore",
|
Name: "keystore",
|
||||||
Value: filepath.Join(node.DefaultDataDir(), "keystore"),
|
Value: filepath.Join(node.DefaultDataDir(), "keystore"),
|
||||||
|
@ -87,7 +93,7 @@ var (
|
||||||
}
|
}
|
||||||
signerSecretFlag = cli.StringFlag{
|
signerSecretFlag = cli.StringFlag{
|
||||||
Name: "signersecret",
|
Name: "signersecret",
|
||||||
Usage: "A file containing the password used to encrypt Clef credentials, e.g. keystore credentials and ruleset hash",
|
Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash",
|
||||||
}
|
}
|
||||||
dBFlag = cli.StringFlag{
|
dBFlag = cli.StringFlag{
|
||||||
Name: "4bytedb",
|
Name: "4bytedb",
|
||||||
|
@ -151,18 +157,18 @@ Whenever you make an edit to the rule file, you need to use attestation to tell
|
||||||
Clef that the file is 'safe' to execute.`,
|
Clef that the file is 'safe' to execute.`,
|
||||||
}
|
}
|
||||||
|
|
||||||
addCredentialCommand = cli.Command{
|
setCredentialCommand = cli.Command{
|
||||||
Action: utils.MigrateFlags(addCredential),
|
Action: utils.MigrateFlags(setCredential),
|
||||||
Name: "addpw",
|
Name: "setpw",
|
||||||
Usage: "Store a credential for a keystore file",
|
Usage: "Store a credential for a keystore file",
|
||||||
ArgsUsage: "<address> <password>",
|
ArgsUsage: "<address>",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
logLevelFlag,
|
logLevelFlag,
|
||||||
configdirFlag,
|
configdirFlag,
|
||||||
signerSecretFlag,
|
signerSecretFlag,
|
||||||
},
|
},
|
||||||
Description: `
|
Description: `
|
||||||
The addpw command stores a password for a given address (keyfile). If you invoke it with only one parameter, it will
|
The setpw command stores a password for a given address (keyfile). If you enter a blank passphrase, it will
|
||||||
remove any stored credential for that address (keyfile)
|
remove any stored credential for that address (keyfile)
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
@ -191,9 +197,10 @@ func init() {
|
||||||
ruleFlag,
|
ruleFlag,
|
||||||
stdiouiFlag,
|
stdiouiFlag,
|
||||||
testFlag,
|
testFlag,
|
||||||
|
advancedMode,
|
||||||
}
|
}
|
||||||
app.Action = signer
|
app.Action = signer
|
||||||
app.Commands = []cli.Command{initCommand, attestCommand, addCredentialCommand}
|
app.Commands = []cli.Command{initCommand, attestCommand, setCredentialCommand}
|
||||||
|
|
||||||
}
|
}
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -207,25 +214,45 @@ func initializeSecrets(c *cli.Context) error {
|
||||||
if err := initialize(c); err != nil {
|
if err := initialize(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
configDir := c.String(configdirFlag.Name)
|
configDir := c.GlobalString(configdirFlag.Name)
|
||||||
|
|
||||||
masterSeed := make([]byte, 256)
|
masterSeed := make([]byte, 256)
|
||||||
n, err := io.ReadFull(rand.Reader, masterSeed)
|
num, err := io.ReadFull(rand.Reader, masterSeed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if n != len(masterSeed) {
|
if num != len(masterSeed) {
|
||||||
return fmt.Errorf("failed to read enough random")
|
return fmt.Errorf("failed to read enough random")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n, p := keystore.StandardScryptN, keystore.StandardScryptP
|
||||||
|
if c.GlobalBool(utils.LightKDFFlag.Name) {
|
||||||
|
n, p = keystore.LightScryptN, keystore.LightScryptP
|
||||||
|
}
|
||||||
|
text := "The master seed of clef is locked with a password. Please give a password. Do not forget this password."
|
||||||
|
var password string
|
||||||
|
for {
|
||||||
|
password = getPassPhrase(text, true)
|
||||||
|
if err := core.ValidatePasswordFormat(password); err != nil {
|
||||||
|
fmt.Printf("invalid password: %v\n", err)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cipherSeed, err := encryptSeed(masterSeed, []byte(password), n, p)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to encrypt master seed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
err = os.Mkdir(configDir, 0700)
|
err = os.Mkdir(configDir, 0700)
|
||||||
if err != nil && !os.IsExist(err) {
|
if err != nil && !os.IsExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
location := filepath.Join(configDir, "secrets.dat")
|
location := filepath.Join(configDir, "masterseed.json")
|
||||||
if _, err := os.Stat(location); err == nil {
|
if _, err := os.Stat(location); err == nil {
|
||||||
return fmt.Errorf("file %v already exists, will not overwrite", location)
|
return fmt.Errorf("file %v already exists, will not overwrite", location)
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(location, masterSeed, 0400)
|
err = ioutil.WriteFile(location, cipherSeed, 0400)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -250,11 +277,11 @@ func attestFile(ctx *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stretchedKey, err := readMasterKey(ctx)
|
stretchedKey, err := readMasterKey(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf(err.Error())
|
utils.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
configDir := ctx.String(configdirFlag.Name)
|
configDir := ctx.GlobalString(configdirFlag.Name)
|
||||||
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
|
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
|
||||||
confKey := crypto.Keccak256([]byte("config"), stretchedKey)
|
confKey := crypto.Keccak256([]byte("config"), stretchedKey)
|
||||||
|
|
||||||
|
@ -266,38 +293,36 @@ func attestFile(ctx *cli.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addCredential(ctx *cli.Context) error {
|
func setCredential(ctx *cli.Context) error {
|
||||||
if len(ctx.Args()) < 1 {
|
if len(ctx.Args()) < 1 {
|
||||||
utils.Fatalf("This command requires at leaste one argument.")
|
utils.Fatalf("This command requires an address to be passed as an argument.")
|
||||||
}
|
}
|
||||||
if err := initialize(ctx); err != nil {
|
if err := initialize(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stretchedKey, err := readMasterKey(ctx)
|
address := ctx.Args().First()
|
||||||
|
password := getPassPhrase("Enter a passphrase to store with this address.", true)
|
||||||
|
|
||||||
|
stretchedKey, err := readMasterKey(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf(err.Error())
|
utils.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
configDir := ctx.String(configdirFlag.Name)
|
configDir := ctx.GlobalString(configdirFlag.Name)
|
||||||
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
|
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
|
||||||
pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
|
pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
|
||||||
|
|
||||||
// Initialize the encrypted storages
|
// Initialize the encrypted storages
|
||||||
pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
|
pwStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "credentials.json"), pwkey)
|
||||||
key := ctx.Args().First()
|
pwStorage.Put(address, password)
|
||||||
value := ""
|
log.Info("Credential store updated", "key", address)
|
||||||
if len(ctx.Args()) > 1 {
|
|
||||||
value = ctx.Args().Get(1)
|
|
||||||
}
|
|
||||||
pwStorage.Put(key, value)
|
|
||||||
log.Info("Credential store updated", "key", key)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func initialize(c *cli.Context) error {
|
func initialize(c *cli.Context) error {
|
||||||
// Set up the logger to print everything
|
// Set up the logger to print everything
|
||||||
logOutput := os.Stdout
|
logOutput := os.Stdout
|
||||||
if c.Bool(stdiouiFlag.Name) {
|
if c.GlobalBool(stdiouiFlag.Name) {
|
||||||
logOutput = os.Stderr
|
logOutput = os.Stderr
|
||||||
// If using the stdioui, we can't do the 'confirm'-flow
|
// If using the stdioui, we can't do the 'confirm'-flow
|
||||||
fmt.Fprintf(logOutput, legalWarning)
|
fmt.Fprintf(logOutput, legalWarning)
|
||||||
|
@ -318,26 +343,28 @@ func signer(c *cli.Context) error {
|
||||||
var (
|
var (
|
||||||
ui core.SignerUI
|
ui core.SignerUI
|
||||||
)
|
)
|
||||||
if c.Bool(stdiouiFlag.Name) {
|
if c.GlobalBool(stdiouiFlag.Name) {
|
||||||
log.Info("Using stdin/stdout as UI-channel")
|
log.Info("Using stdin/stdout as UI-channel")
|
||||||
ui = core.NewStdIOUI()
|
ui = core.NewStdIOUI()
|
||||||
} else {
|
} else {
|
||||||
log.Info("Using CLI as UI-channel")
|
log.Info("Using CLI as UI-channel")
|
||||||
ui = core.NewCommandlineUI()
|
ui = core.NewCommandlineUI()
|
||||||
}
|
}
|
||||||
db, err := core.NewAbiDBFromFiles(c.String(dBFlag.Name), c.String(customDBFlag.Name))
|
fourByteDb := c.GlobalString(dBFlag.Name)
|
||||||
|
fourByteLocal := c.GlobalString(customDBFlag.Name)
|
||||||
|
db, err := core.NewAbiDBFromFiles(fourByteDb, fourByteLocal)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf(err.Error())
|
utils.Fatalf(err.Error())
|
||||||
}
|
}
|
||||||
log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb"))
|
log.Info("Loaded 4byte db", "signatures", db.Size(), "file", fourByteDb, "local", fourByteLocal)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
api core.ExternalAPI
|
api core.ExternalAPI
|
||||||
)
|
)
|
||||||
|
|
||||||
configDir := c.String(configdirFlag.Name)
|
configDir := c.GlobalString(configdirFlag.Name)
|
||||||
if stretchedKey, err := readMasterKey(c); err != nil {
|
if stretchedKey, err := readMasterKey(c, ui); err != nil {
|
||||||
log.Info("No master seed provided, rules disabled")
|
log.Info("No master seed provided, rules disabled", "error", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -356,7 +383,7 @@ func signer(c *cli.Context) error {
|
||||||
configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
|
configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
|
||||||
|
|
||||||
//Do we have a rule-file?
|
//Do we have a rule-file?
|
||||||
ruleJS, err := ioutil.ReadFile(c.String(ruleFlag.Name))
|
ruleJS, err := ioutil.ReadFile(c.GlobalString(ruleFlag.Name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Info("Could not load rulefile, rules not enabled", "file", "rulefile")
|
log.Info("Could not load rulefile, rules not enabled", "file", "rulefile")
|
||||||
} else {
|
} else {
|
||||||
|
@ -380,16 +407,15 @@ func signer(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
apiImpl := core.NewSignerAPI(
|
apiImpl := core.NewSignerAPI(
|
||||||
c.Int64(utils.NetworkIdFlag.Name),
|
c.GlobalInt64(utils.NetworkIdFlag.Name),
|
||||||
c.String(keystoreFlag.Name),
|
c.GlobalString(keystoreFlag.Name),
|
||||||
c.Bool(utils.NoUSBFlag.Name),
|
c.GlobalBool(utils.NoUSBFlag.Name),
|
||||||
ui, db,
|
ui, db,
|
||||||
c.Bool(utils.LightKDFFlag.Name))
|
c.GlobalBool(utils.LightKDFFlag.Name),
|
||||||
|
c.GlobalBool(advancedMode.Name))
|
||||||
api = apiImpl
|
api = apiImpl
|
||||||
|
|
||||||
// Audit logging
|
// Audit logging
|
||||||
if logfile := c.String(auditLogFlag.Name); logfile != "" {
|
if logfile := c.GlobalString(auditLogFlag.Name); logfile != "" {
|
||||||
api, err = core.NewAuditLogger(logfile, api)
|
api, err = core.NewAuditLogger(logfile, api)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf(err.Error())
|
utils.Fatalf(err.Error())
|
||||||
|
@ -408,13 +434,13 @@ func signer(c *cli.Context) error {
|
||||||
Service: api,
|
Service: api,
|
||||||
Version: "1.0"},
|
Version: "1.0"},
|
||||||
}
|
}
|
||||||
if c.Bool(utils.RPCEnabledFlag.Name) {
|
if c.GlobalBool(utils.RPCEnabledFlag.Name) {
|
||||||
|
|
||||||
vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name))
|
vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name))
|
||||||
cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name))
|
cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name))
|
||||||
|
|
||||||
// start http server
|
// start http server
|
||||||
httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
|
httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
|
||||||
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
|
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Could not start RPC api: %v", err)
|
utils.Fatalf("Could not start RPC api: %v", err)
|
||||||
|
@ -428,9 +454,9 @@ func signer(c *cli.Context) error {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
}
|
}
|
||||||
if !c.Bool(utils.IPCDisabledFlag.Name) {
|
if !c.GlobalBool(utils.IPCDisabledFlag.Name) {
|
||||||
if c.IsSet(utils.IPCPathFlag.Name) {
|
if c.IsSet(utils.IPCPathFlag.Name) {
|
||||||
ipcapiURL = c.String(utils.IPCPathFlag.Name)
|
ipcapiURL = c.GlobalString(utils.IPCPathFlag.Name)
|
||||||
} else {
|
} else {
|
||||||
ipcapiURL = filepath.Join(configDir, "clef.ipc")
|
ipcapiURL = filepath.Join(configDir, "clef.ipc")
|
||||||
}
|
}
|
||||||
|
@ -447,7 +473,7 @@ func signer(c *cli.Context) error {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Bool(testFlag.Name) {
|
if c.GlobalBool(testFlag.Name) {
|
||||||
log.Info("Performing UI test")
|
log.Info("Performing UI test")
|
||||||
go testExternalUI(apiImpl)
|
go testExternalUI(apiImpl)
|
||||||
}
|
}
|
||||||
|
@ -506,36 +532,52 @@ func homeDir() string {
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
func readMasterKey(ctx *cli.Context) ([]byte, error) {
|
func readMasterKey(ctx *cli.Context, ui core.SignerUI) ([]byte, error) {
|
||||||
var (
|
var (
|
||||||
file string
|
file string
|
||||||
configDir = ctx.String(configdirFlag.Name)
|
configDir = ctx.GlobalString(configdirFlag.Name)
|
||||||
)
|
)
|
||||||
if ctx.IsSet(signerSecretFlag.Name) {
|
if ctx.GlobalIsSet(signerSecretFlag.Name) {
|
||||||
file = ctx.String(signerSecretFlag.Name)
|
file = ctx.GlobalString(signerSecretFlag.Name)
|
||||||
} else {
|
} else {
|
||||||
file = filepath.Join(configDir, "secrets.dat")
|
file = filepath.Join(configDir, "masterseed.json")
|
||||||
}
|
}
|
||||||
if err := checkFile(file); err != nil {
|
if err := checkFile(file); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
masterKey, err := ioutil.ReadFile(file)
|
cipherKey, err := ioutil.ReadFile(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(masterKey) < 256 {
|
var password string
|
||||||
return nil, fmt.Errorf("master key of insufficient length, expected >255 bytes, got %d", len(masterKey))
|
// If ui is not nil, get the password from ui.
|
||||||
|
if ui != nil {
|
||||||
|
resp, err := ui.OnInputRequired(core.UserInputRequest{
|
||||||
|
Title: "Master Password",
|
||||||
|
Prompt: "Please enter the password to decrypt the master seed",
|
||||||
|
IsPassword: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
password = resp.Text
|
||||||
|
} else {
|
||||||
|
password = getPassPhrase("Decrypt master seed of clef", false)
|
||||||
|
}
|
||||||
|
masterSeed, err := decryptSeed(cipherKey, password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decrypt the master seed of clef")
|
||||||
|
}
|
||||||
|
if len(masterSeed) < 256 {
|
||||||
|
return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
|
||||||
|
}
|
||||||
|
|
||||||
// Create vault location
|
// Create vault location
|
||||||
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterKey)[:10]))
|
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10]))
|
||||||
err = os.Mkdir(vaultLocation, 0700)
|
err = os.Mkdir(vaultLocation, 0700)
|
||||||
if err != nil && !os.IsExist(err) {
|
if err != nil && !os.IsExist(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
//!TODO, use KDF to stretch the master key
|
return masterSeed, nil
|
||||||
// stretched_key := stretch_key(master_key)
|
|
||||||
|
|
||||||
return masterKey, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkFile is a convenience function to check if a file
|
// checkFile is a convenience function to check if a file
|
||||||
|
@ -613,6 +655,59 @@ func testExternalUI(api *core.SignerAPI) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getPassPhrase retrieves the password associated with clef, either fetched
|
||||||
|
// from a list of preloaded passphrases, or requested interactively from the user.
|
||||||
|
// TODO: there are many `getPassPhrase` functions, it will be better to abstract them into one.
|
||||||
|
func getPassPhrase(prompt string, confirmation bool) string {
|
||||||
|
fmt.Println(prompt)
|
||||||
|
password, err := console.Stdin.PromptPassword("Passphrase: ")
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read passphrase: %v", err)
|
||||||
|
}
|
||||||
|
if confirmation {
|
||||||
|
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
|
||||||
|
}
|
||||||
|
if password != confirm {
|
||||||
|
utils.Fatalf("Passphrases do not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
|
||||||
|
type encryptedSeedStorage struct {
|
||||||
|
Description string `json:"description"`
|
||||||
|
Version int `json:"version"`
|
||||||
|
Params keystore.CryptoJSON `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping,
|
||||||
|
// to encrypt the master seed
|
||||||
|
func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) {
|
||||||
|
cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct})
|
||||||
|
}
|
||||||
|
|
||||||
|
// decryptSeed decrypts the master seed
|
||||||
|
func decryptSeed(keyjson []byte, auth string) ([]byte, error) {
|
||||||
|
var encSeed encryptedSeedStorage
|
||||||
|
if err := json.Unmarshal(keyjson, &encSeed); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if encSeed.Version != 1 {
|
||||||
|
log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version))
|
||||||
|
}
|
||||||
|
seed, err := keystore.DecryptDataV3(encSeed.Params, auth)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return seed, err
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
//Create Account
|
//Create Account
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 20 KiB |
|
@ -31,43 +31,51 @@ NOTE: This file does not contain your accounts. Those need to be backed up separ
|
||||||
|
|
||||||
## Creating rules
|
## Creating rules
|
||||||
|
|
||||||
Now, you can create a rule-file.
|
Now, you can create a rule-file. Note that it is not mandatory to use predefined rules, but it's really handy.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function ApproveListing(){
|
function ApproveListing(){
|
||||||
return "Approve"
|
return "Approve"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Get the `sha256` hash....
|
|
||||||
|
Get the `sha256` hash. If you have openssl, you can do `openssl sha256 rules.js`...
|
||||||
```text
|
```text
|
||||||
#sha256sum rules.js
|
#sha256sum rules.js
|
||||||
6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72 rules.js
|
6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72 rules.js
|
||||||
```
|
```
|
||||||
...And then `attest` the file:
|
...now `attest` the file...
|
||||||
```text
|
```text
|
||||||
#./signer attest 6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72
|
#./signer attest 6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72
|
||||||
|
|
||||||
INFO [02-21|12:14:38] Ruleset attestation updated sha256=6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72
|
INFO [02-21|12:14:38] Ruleset attestation updated sha256=6c21d1737429d6d4f2e55146da0797782f3c0a0355227f19d702df377c165d72
|
||||||
```
|
```
|
||||||
At this point, we then start the signer with the rule-file:
|
|
||||||
|
|
||||||
|
...and (this is required only for non-production versions) load a mock-up `4byte.json` by copying the file from the source to your current working directory:
|
||||||
```text
|
```text
|
||||||
#./signer --rules rules.json
|
#cp $GOPATH/src/github.com/ethereum/go-ethereum/cmd/clef/4byte.json $PWD
|
||||||
|
```
|
||||||
|
|
||||||
INFO [02-21|12:15:18] Using CLI as UI-channel
|
At this point, we can start the signer with the rule-file:
|
||||||
INFO [02-21|12:15:18] Loaded 4byte db signatures=5509 file=./4byte.json
|
```text
|
||||||
INFO [02-21|12:15:18] Could not load rulefile, rules not enabled file=rulefile
|
#./signer --rules rules.js --rpc
|
||||||
DEBUG[02-21|12:15:18] FS scan times list=35.335µs set=5.536µs diff=5.073µs
|
|
||||||
DEBUG[02-21|12:15:18] Ledger support enabled
|
INFO [09-25|20:28:11.866] Using CLI as UI-channel
|
||||||
DEBUG[02-21|12:15:18] Trezor support enabled
|
INFO [09-25|20:28:11.876] Loaded 4byte db signatures=5509 file=./4byte.json
|
||||||
INFO [02-21|12:15:18] Audit logs configured file=audit.log
|
INFO [09-25|20:28:11.877] Rule engine configured file=./rules.js
|
||||||
INFO [02-21|12:15:18] HTTP endpoint opened url=http://localhost:8550
|
DEBUG[09-25|20:28:11.877] FS scan times list=100.781µs set=13.253µs diff=5.761µs
|
||||||
|
DEBUG[09-25|20:28:11.884] Ledger support enabled
|
||||||
|
DEBUG[09-25|20:28:11.888] Trezor support enabled
|
||||||
|
INFO [09-25|20:28:11.888] Audit logs configured file=audit.log
|
||||||
|
DEBUG[09-25|20:28:11.888] HTTP registered namespace=account
|
||||||
|
INFO [09-25|20:28:11.890] HTTP endpoint opened url=http://localhost:8550
|
||||||
|
DEBUG[09-25|20:28:11.890] IPC registered namespace=account
|
||||||
|
INFO [09-25|20:28:11.890] IPC endpoint opened url=<nil>
|
||||||
------- Signer info -------
|
------- Signer info -------
|
||||||
|
* extapi_version : 2.0.0
|
||||||
|
* intapi_version : 2.0.0
|
||||||
* extapi_http : http://localhost:8550
|
* extapi_http : http://localhost:8550
|
||||||
* extapi_ipc : <nil>
|
* extapi_ipc : <nil>
|
||||||
* extapi_version : 2.0.0
|
|
||||||
* intapi_version : 1.2.0
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Any list-requests will now be auto-approved by our rule-file.
|
Any list-requests will now be auto-approved by our rule-file.
|
||||||
|
@ -107,16 +115,16 @@ The `master_seed` was then used to derive a few other things:
|
||||||
|
|
||||||
## Adding credentials
|
## Adding credentials
|
||||||
|
|
||||||
In order to make more useful rules; sign transactions, the signer needs access to the passwords needed to unlock keystores.
|
In order to make more useful rules like signing transactions, the signer needs access to the passwords needed to unlock keystores.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
#./signer addpw 0x694267f14675d7e1b9494fd8d72fefe1755710fa test
|
#./signer addpw "0x694267f14675d7e1b9494fd8d72fefe1755710fa" "test_password"
|
||||||
|
|
||||||
INFO [02-21|13:43:21] Credential store updated key=0x694267f14675d7e1b9494fd8d72fefe1755710fa
|
INFO [02-21|13:43:21] Credential store updated key=0x694267f14675d7e1b9494fd8d72fefe1755710fa
|
||||||
```
|
```
|
||||||
## More advanced rules
|
## More advanced rules
|
||||||
|
|
||||||
Now let's update the rules to make use of credentials
|
Now let's update the rules to make use of credentials:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function ApproveListing(){
|
function ApproveListing(){
|
||||||
|
@ -134,13 +142,15 @@ function ApproveSignData(r){
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
In this example,
|
In this example:
|
||||||
* any requests to sign data with the account `0x694...` will be
|
* Any requests to sign data with the account `0x694...` will be
|
||||||
* auto-approved if the message contains with `bazonk`,
|
* auto-approved if the message contains with `bazonk`
|
||||||
* and auto-rejected if it does not.
|
* auto-rejected if it does not.
|
||||||
* Any other signing-requests will be passed along for manual approve/reject.
|
* Any other signing-requests will be passed along for manual approve/reject.
|
||||||
|
|
||||||
..attest the new file
|
_Note: make sure that `0x694...` is an account you have access to. You can create it either via the clef or the traditional account cli tool. If the latter was chosen, make sure both clef and geth use the same keystore by specifing `--keystore path/to/your/keystore` when running clef._
|
||||||
|
|
||||||
|
Attest the new file...
|
||||||
```text
|
```text
|
||||||
#sha256sum rules.js
|
#sha256sum rules.js
|
||||||
2a0cb661dacfc804b6e95d935d813fd17c0997a7170e4092ffbc34ca976acd9f rules.js
|
2a0cb661dacfc804b6e95d935d813fd17c0997a7170e4092ffbc34ca976acd9f rules.js
|
||||||
|
@ -153,23 +163,26 @@ INFO [02-21|14:36:30] Ruleset attestation updated sha256=2a0cb661da
|
||||||
And start the signer:
|
And start the signer:
|
||||||
|
|
||||||
```
|
```
|
||||||
#./signer --rules rules.js
|
#./signer --rules rules.js --rpc
|
||||||
|
|
||||||
INFO [02-21|14:41:56] Using CLI as UI-channel
|
INFO [09-25|21:02:16.450] Using CLI as UI-channel
|
||||||
INFO [02-21|14:41:56] Loaded 4byte db signatures=5509 file=./4byte.json
|
INFO [09-25|21:02:16.466] Loaded 4byte db signatures=5509 file=./4byte.json
|
||||||
INFO [02-21|14:41:56] Rule engine configured file=rules.js
|
INFO [09-25|21:02:16.467] Rule engine configured file=./rules.js
|
||||||
DEBUG[02-21|14:41:56] FS scan times list=34.607µs set=4.509µs diff=4.87µs
|
DEBUG[09-25|21:02:16.468] FS scan times list=1.45262ms set=21.926µs diff=6.944µs
|
||||||
DEBUG[02-21|14:41:56] Ledger support enabled
|
DEBUG[09-25|21:02:16.473] Ledger support enabled
|
||||||
DEBUG[02-21|14:41:56] Trezor support enabled
|
DEBUG[09-25|21:02:16.475] Trezor support enabled
|
||||||
INFO [02-21|14:41:56] Audit logs configured file=audit.log
|
INFO [09-25|21:02:16.476] Audit logs configured file=audit.log
|
||||||
INFO [02-21|14:41:56] HTTP endpoint opened url=http://localhost:8550
|
DEBUG[09-25|21:02:16.476] HTTP registered namespace=account
|
||||||
|
INFO [09-25|21:02:16.478] HTTP endpoint opened url=http://localhost:8550
|
||||||
|
DEBUG[09-25|21:02:16.478] IPC registered namespace=account
|
||||||
|
INFO [09-25|21:02:16.478] IPC endpoint opened url=<nil>
|
||||||
------- Signer info -------
|
------- Signer info -------
|
||||||
* extapi_version : 2.0.0
|
* extapi_version : 2.0.0
|
||||||
* intapi_version : 1.2.0
|
* intapi_version : 2.0.0
|
||||||
* extapi_http : http://localhost:8550
|
* extapi_http : http://localhost:8550
|
||||||
* extapi_ipc : <nil>
|
* extapi_ipc : <nil>
|
||||||
INFO [02-21|14:41:56] error occurred during execution error="ReferenceError: 'OnSignerStartup' is not defined"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And then test signing, once with `bazonk` and once without:
|
And then test signing, once with `bazonk` and once without:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -190,7 +203,7 @@ INFO [02-21|14:42:56] Op rejected
|
||||||
The signer also stores all traffic over the external API in a log file. The last 4 lines shows the two requests and their responses:
|
The signer also stores all traffic over the external API in a log file. The last 4 lines shows the two requests and their responses:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
#tail audit.log -n 4
|
#tail -n 4 audit.log
|
||||||
t=2018-02-21T14:42:41+0100 lvl=info msg=Sign api=signer type=request metadata="{\"remote\":\"127.0.0.1:49706\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\"}" addr="0x694267f14675d7e1b9494fd8d72fefe1755710fa [chksum INVALID]" data=202062617a6f6e6b2062617a2067617a0a
|
t=2018-02-21T14:42:41+0100 lvl=info msg=Sign api=signer type=request metadata="{\"remote\":\"127.0.0.1:49706\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\"}" addr="0x694267f14675d7e1b9494fd8d72fefe1755710fa [chksum INVALID]" data=202062617a6f6e6b2062617a2067617a0a
|
||||||
t=2018-02-21T14:42:42+0100 lvl=info msg=Sign api=signer type=response data=93e6161840c3ae1efc26dc68dedab6e8fc233bb3fefa1b4645dbf6609b93dace160572ea4ab33240256bb6d3dadb60dcd9c515d6374d3cf614ee897408d41d541c error=nil
|
t=2018-02-21T14:42:42+0100 lvl=info msg=Sign api=signer type=response data=93e6161840c3ae1efc26dc68dedab6e8fc233bb3fefa1b4645dbf6609b93dace160572ea4ab33240256bb6d3dadb60dcd9c515d6374d3cf614ee897408d41d541c error=nil
|
||||||
t=2018-02-21T14:42:56+0100 lvl=info msg=Sign api=signer type=request metadata="{\"remote\":\"127.0.0.1:49708\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\"}" addr="0x694267f14675d7e1b9494fd8d72fefe1755710fa [chksum INVALID]" data=2020626f6e6b2062617a2067617a0a
|
t=2018-02-21T14:42:56+0100 lvl=info msg=Sign api=signer type=request metadata="{\"remote\":\"127.0.0.1:49708\",\"local\":\"localhost:8550\",\"scheme\":\"HTTP/1.1\"}" addr="0x694267f14675d7e1b9494fd8d72fefe1755710fa [chksum INVALID]" data=2020626f6e6b2062617a2067617a0a
|
||||||
|
|
|
@ -52,6 +52,7 @@ func (l *JSONLogger) CaptureState(env *vm.EVM, pc uint64, op vm.OpCode, gas, cos
|
||||||
MemorySize: memory.Len(),
|
MemorySize: memory.Len(),
|
||||||
Storage: nil,
|
Storage: nil,
|
||||||
Depth: depth,
|
Depth: depth,
|
||||||
|
RefundCounter: env.StateDB.GetRefund(),
|
||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
if !l.cfg.DisableMemory {
|
if !l.cfg.DisableMemory {
|
||||||
|
|
|
@ -97,6 +97,10 @@ func stateTestCmd(ctx *cli.Context) error {
|
||||||
// Run the test and aggregate the result
|
// Run the test and aggregate the result
|
||||||
result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true}
|
result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true}
|
||||||
state, err := test.Run(st, cfg)
|
state, err := test.Run(st, cfg)
|
||||||
|
// print state root for evmlab tracing
|
||||||
|
if ctx.GlobalBool(MachineFlag.Name) && state != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Test failed, mark as so and dump any state to aid debugging
|
// Test failed, mark as so and dump any state to aid debugging
|
||||||
result.Pass, result.Error = false, err.Error()
|
result.Pass, result.Error = false, err.Error()
|
||||||
|
@ -105,10 +109,6 @@ func stateTestCmd(ctx *cli.Context) error {
|
||||||
result.State = &dump
|
result.State = &dump
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// print state root for evmlab tracing (already committed above, so no need to delete objects again
|
|
||||||
if ctx.GlobalBool(MachineFlag.Name) && state != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false))
|
|
||||||
}
|
|
||||||
|
|
||||||
results = append(results, *result)
|
results = append(results, *result)
|
||||||
|
|
||||||
|
|
|
@ -54,8 +54,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"golang.org/x/net/websocket"
|
"golang.org/x/net/websocket"
|
||||||
|
@ -255,9 +255,11 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, boot := range enodes {
|
for _, boot := range enodes {
|
||||||
old, _ := discover.ParseNode(boot.String())
|
old, err := enode.ParseV4(boot.String())
|
||||||
|
if err != nil {
|
||||||
stack.Server().AddPeer(old)
|
stack.Server().AddPeer(old)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Attach to the client and retrieve and interesting metadatas
|
// Attach to the client and retrieve and interesting metadatas
|
||||||
api, err := stack.Attach()
|
api, err := stack.Attach()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
godebug "runtime/debug"
|
godebug "runtime/debug"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -218,8 +217,6 @@ func init() {
|
||||||
app.Flags = append(app.Flags, metricsFlags...)
|
app.Flags = append(app.Flags, metricsFlags...)
|
||||||
|
|
||||||
app.Before = func(ctx *cli.Context) error {
|
app.Before = func(ctx *cli.Context) error {
|
||||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
|
||||||
|
|
||||||
logdir := ""
|
logdir := ""
|
||||||
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
|
||||||
logdir = (&node.Config{DataDir: utils.MakeDataDir(ctx)}).ResolvePath("logs")
|
logdir = (&node.Config{DataDir: utils.MakeDataDir(ctx)}).ResolvePath("logs")
|
||||||
|
|
|
@ -47,7 +47,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations"
|
"github.com/ethereum/go-ethereum/p2p/simulations"
|
||||||
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
"github.com/ethereum/go-ethereum/p2p/simulations/adapters"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
@ -285,7 +285,7 @@ func createNode(ctx *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
config.ID = discover.PubkeyID(&privKey.PublicKey)
|
config.ID = enode.PubkeyToIDV4(&privKey.PublicKey)
|
||||||
config.PrivateKey = privKey
|
config.PrivateKey = privKey
|
||||||
}
|
}
|
||||||
if services := ctx.String("services"); services != "" {
|
if services := ctx.String("services"); services != "" {
|
||||||
|
|
|
@ -42,7 +42,7 @@ ADD genesis.json /genesis.json
|
||||||
RUN \
|
RUN \
|
||||||
echo 'geth --cache 512 init /genesis.json' > geth.sh && \{{if .Unlock}}
|
echo 'geth --cache 512 init /genesis.json' > geth.sh && \{{if .Unlock}}
|
||||||
echo 'mkdir -p /root/.ethereum/keystore/ && cp /signer.json /root/.ethereum/keystore/' >> geth.sh && \{{end}}
|
echo 'mkdir -p /root/.ethereum/keystore/ && cp /signer.json /root/.ethereum/keystore/' >> geth.sh && \{{end}}
|
||||||
echo $'exec geth --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .Bootnodes}}--bootnodes {{.Bootnodes}}{{end}} {{if .Etherbase}}--miner.etherbase {{.Etherbase}} --mine --miner.threads 1{{end}} {{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --miner.gastarget {{.GasTarget}} --miner.gaslimit {{.GasLimit}} --miner.gasprice {{.GasPrice}}' >> geth.sh
|
echo $'exec geth --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --nat extip:{{.IP}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .Bootnodes}}--bootnodes {{.Bootnodes}}{{end}} {{if .Etherbase}}--miner.etherbase {{.Etherbase}} --mine --miner.threads 1{{end}} {{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --miner.gastarget {{.GasTarget}} --miner.gaslimit {{.GasLimit}} --miner.gasprice {{.GasPrice}}' >> geth.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/sh", "geth.sh"]
|
ENTRYPOINT ["/bin/sh", "geth.sh"]
|
||||||
`
|
`
|
||||||
|
@ -99,6 +99,7 @@ func deployNode(client *sshClient, network string, bootnodes []string, config *n
|
||||||
template.Must(template.New("").Parse(nodeDockerfile)).Execute(dockerfile, map[string]interface{}{
|
template.Must(template.New("").Parse(nodeDockerfile)).Execute(dockerfile, map[string]interface{}{
|
||||||
"NetworkID": config.network,
|
"NetworkID": config.network,
|
||||||
"Port": config.port,
|
"Port": config.port,
|
||||||
|
"IP": client.address,
|
||||||
"Peers": config.peersTotal,
|
"Peers": config.peersTotal,
|
||||||
"LightFlag": lightFlag,
|
"LightFlag": lightFlag,
|
||||||
"Bootnodes": strings.Join(bootnodes, ","),
|
"Bootnodes": strings.Join(bootnodes, ","),
|
||||||
|
@ -227,10 +228,10 @@ func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error)
|
||||||
|
|
||||||
// Container available, retrieve its node ID and its genesis json
|
// Container available, retrieve its node ID and its genesis json
|
||||||
var out []byte
|
var out []byte
|
||||||
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 geth --exec admin.nodeInfo.id --cache=16 attach", network, kind)); err != nil {
|
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 geth --exec admin.nodeInfo.enode --cache=16 attach", network, kind)); err != nil {
|
||||||
return nil, ErrServiceUnreachable
|
return nil, ErrServiceUnreachable
|
||||||
}
|
}
|
||||||
id := bytes.Trim(bytes.TrimSpace(out), "\"")
|
enode := bytes.Trim(bytes.TrimSpace(out), "\"")
|
||||||
|
|
||||||
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /genesis.json", network, kind)); err != nil {
|
if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /genesis.json", network, kind)); err != nil {
|
||||||
return nil, ErrServiceUnreachable
|
return nil, ErrServiceUnreachable
|
||||||
|
@ -265,7 +266,7 @@ func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error)
|
||||||
gasLimit: gasLimit,
|
gasLimit: gasLimit,
|
||||||
gasPrice: gasPrice,
|
gasPrice: gasPrice,
|
||||||
}
|
}
|
||||||
stats.enode = fmt.Sprintf("enode://%s@%s:%d", id, client.address, stats.port)
|
stats.enode = string(enode)
|
||||||
|
|
||||||
return stats, nil
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,65 @@ import (
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var salt = make([]byte, 32)
|
var (
|
||||||
|
salt = make([]byte, 32)
|
||||||
|
accessCommand = cli.Command{
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "access",
|
||||||
|
Usage: "encrypts a reference and embeds it into a root manifest",
|
||||||
|
ArgsUsage: "<ref>",
|
||||||
|
Description: "encrypts a reference and embeds it into a root manifest",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "new",
|
||||||
|
Usage: "encrypts a reference and embeds it into a root manifest",
|
||||||
|
ArgsUsage: "<ref>",
|
||||||
|
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Action: accessNewPass,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.PasswordFileFlag,
|
||||||
|
SwarmDryRunFlag,
|
||||||
|
},
|
||||||
|
Name: "pass",
|
||||||
|
Usage: "encrypts a reference with a password and embeds it into a root manifest",
|
||||||
|
ArgsUsage: "<ref>",
|
||||||
|
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: accessNewPK,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.PasswordFileFlag,
|
||||||
|
SwarmDryRunFlag,
|
||||||
|
SwarmAccessGrantKeyFlag,
|
||||||
|
},
|
||||||
|
Name: "pk",
|
||||||
|
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
|
||||||
|
ArgsUsage: "<ref>",
|
||||||
|
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: accessNewACT,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
SwarmAccessGrantKeysFlag,
|
||||||
|
SwarmDryRunFlag,
|
||||||
|
utils.PasswordFileFlag,
|
||||||
|
},
|
||||||
|
Name: "act",
|
||||||
|
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
|
||||||
|
ArgsUsage: "<ref>",
|
||||||
|
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||||
|
@ -56,6 +114,9 @@ func accessNewPass(ctx *cli.Context) {
|
||||||
utils.Fatalf("error getting session key: %v", err)
|
utils.Fatalf("error getting session key: %v", err)
|
||||||
}
|
}
|
||||||
m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
|
m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("had an error generating the manifest: %v", err)
|
||||||
|
}
|
||||||
if dryRun {
|
if dryRun {
|
||||||
err = printManifests(m, nil)
|
err = printManifests(m, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -89,6 +150,9 @@ func accessNewPK(ctx *cli.Context) {
|
||||||
utils.Fatalf("error getting session key: %v", err)
|
utils.Fatalf("error getting session key: %v", err)
|
||||||
}
|
}
|
||||||
m, err := api.GenerateAccessControlManifest(ctx, ref, sessionKey, ae)
|
m, err := api.GenerateAccessControlManifest(ctx, ref, sessionKey, ae)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("had an error generating the manifest: %v", err)
|
||||||
|
}
|
||||||
if dryRun {
|
if dryRun {
|
||||||
err = printManifests(m, nil)
|
err = printManifests(m, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -130,7 +194,7 @@ func accessNewACT(ctx *cli.Context) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("had an error reading the grantee public key list")
|
utils.Fatalf("had an error reading the grantee public key list")
|
||||||
}
|
}
|
||||||
pkGrantees = strings.Split(string(bytes), "\n")
|
pkGrantees = strings.Split(strings.Trim(string(bytes), "\n"), "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if passGranteesFilename != "" {
|
if passGranteesFilename != "" {
|
||||||
|
@ -138,7 +202,7 @@ func accessNewACT(ctx *cli.Context) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("could not read password filename: %v", err)
|
utils.Fatalf("could not read password filename: %v", err)
|
||||||
}
|
}
|
||||||
passGrantees = strings.Split(string(bytes), "\n")
|
passGrantees = strings.Split(strings.Trim(string(bytes), "\n"), "\n")
|
||||||
}
|
}
|
||||||
accessKey, ae, actManifest, err = api.DoACT(ctx, privateKey, salt, pkGrantees, passGrantees)
|
accessKey, ae, actManifest, err = api.DoACT(ctx, privateKey, salt, pkGrantees, passGrantees)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||||
|
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||||
"github.com/ethereum/go-ethereum/swarm/testutil"
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,9 +55,8 @@ var DefaultCurve = crypto.S256()
|
||||||
// is then fetched through 2nd node. since the tested code is not key-aware - we can just
|
// is then fetched through 2nd node. since the tested code is not key-aware - we can just
|
||||||
// fetch from the 2nd node using HTTP BasicAuth
|
// fetch from the 2nd node using HTTP BasicAuth
|
||||||
func TestAccessPassword(t *testing.T) {
|
func TestAccessPassword(t *testing.T) {
|
||||||
cluster := newTestCluster(t, 1)
|
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||||
defer cluster.Shutdown()
|
defer srv.Close()
|
||||||
proxyNode := cluster.Nodes[0]
|
|
||||||
|
|
||||||
dataFilename := testutil.TempFileWithContent(t, data)
|
dataFilename := testutil.TempFileWithContent(t, data)
|
||||||
defer os.RemoveAll(dataFilename)
|
defer os.RemoveAll(dataFilename)
|
||||||
|
@ -64,7 +64,7 @@ func TestAccessPassword(t *testing.T) {
|
||||||
// upload the file with 'swarm up' and expect a hash
|
// upload the file with 'swarm up' and expect a hash
|
||||||
up := runSwarm(t,
|
up := runSwarm(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
proxyNode.URL, //it doesn't matter through which node we upload content
|
srv.URL, //it doesn't matter through which node we upload content
|
||||||
"up",
|
"up",
|
||||||
"--encrypt",
|
"--encrypt",
|
||||||
dataFilename)
|
dataFilename)
|
||||||
|
@ -138,7 +138,7 @@ func TestAccessPassword(t *testing.T) {
|
||||||
if a.Publisher != "" {
|
if a.Publisher != "" {
|
||||||
t.Fatal("should be empty")
|
t.Fatal("should be empty")
|
||||||
}
|
}
|
||||||
client := swarm.NewClient(cluster.Nodes[0].URL)
|
client := swarm.NewClient(srv.URL)
|
||||||
|
|
||||||
hash, err := client.UploadManifest(&m, false)
|
hash, err := client.UploadManifest(&m, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -147,7 +147,7 @@ func TestAccessPassword(t *testing.T) {
|
||||||
|
|
||||||
httpClient := &http.Client{}
|
httpClient := &http.Client{}
|
||||||
|
|
||||||
url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash
|
url := srv.URL + "/" + "bzz:/" + hash
|
||||||
response, err := httpClient.Get(url)
|
response, err := httpClient.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -189,7 +189,7 @@ func TestAccessPassword(t *testing.T) {
|
||||||
//download file with 'swarm down' with wrong password
|
//download file with 'swarm down' with wrong password
|
||||||
up = runSwarm(t,
|
up = runSwarm(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
proxyNode.URL,
|
srv.URL,
|
||||||
"down",
|
"down",
|
||||||
"bzz:/"+hash,
|
"bzz:/"+hash,
|
||||||
tmp,
|
tmp,
|
||||||
|
|
|
@ -68,6 +68,7 @@ const (
|
||||||
SWARM_ENV_SWAP_API = "SWARM_SWAP_API"
|
SWARM_ENV_SWAP_API = "SWARM_SWAP_API"
|
||||||
SWARM_ENV_SYNC_DISABLE = "SWARM_SYNC_DISABLE"
|
SWARM_ENV_SYNC_DISABLE = "SWARM_SYNC_DISABLE"
|
||||||
SWARM_ENV_SYNC_UPDATE_DELAY = "SWARM_ENV_SYNC_UPDATE_DELAY"
|
SWARM_ENV_SYNC_UPDATE_DELAY = "SWARM_ENV_SYNC_UPDATE_DELAY"
|
||||||
|
SWARM_ENV_MAX_STREAM_PEER_SERVERS = "SWARM_ENV_MAX_STREAM_PEER_SERVERS"
|
||||||
SWARM_ENV_LIGHT_NODE_ENABLE = "SWARM_LIGHT_NODE_ENABLE"
|
SWARM_ENV_LIGHT_NODE_ENABLE = "SWARM_LIGHT_NODE_ENABLE"
|
||||||
SWARM_ENV_DELIVERY_SKIP_CHECK = "SWARM_DELIVERY_SKIP_CHECK"
|
SWARM_ENV_DELIVERY_SKIP_CHECK = "SWARM_DELIVERY_SKIP_CHECK"
|
||||||
SWARM_ENV_ENS_API = "SWARM_ENS_API"
|
SWARM_ENV_ENS_API = "SWARM_ENS_API"
|
||||||
|
@ -79,6 +80,7 @@ const (
|
||||||
SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY"
|
SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY"
|
||||||
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
|
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
|
||||||
SWARM_ACCESS_PASSWORD = "SWARM_ACCESS_PASSWORD"
|
SWARM_ACCESS_PASSWORD = "SWARM_ACCESS_PASSWORD"
|
||||||
|
SWARM_AUTO_DEFAULTPATH = "SWARM_AUTO_DEFAULTPATH"
|
||||||
GETH_ENV_DATADIR = "GETH_DATADIR"
|
GETH_ENV_DATADIR = "GETH_DATADIR"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -124,7 +126,7 @@ func initSwarmNode(config *bzzapi.Config, stack *node.Node, ctx *cli.Context) {
|
||||||
//get the account for the provided swarm account
|
//get the account for the provided swarm account
|
||||||
prvkey := getAccount(config.BzzAccount, ctx, stack)
|
prvkey := getAccount(config.BzzAccount, ctx, stack)
|
||||||
//set the resolved config path (geth --datadir)
|
//set the resolved config path (geth --datadir)
|
||||||
config.Path = stack.InstanceDir()
|
config.Path = expandPath(stack.InstanceDir())
|
||||||
//finally, initialize the configuration
|
//finally, initialize the configuration
|
||||||
config.Init(prvkey)
|
config.Init(prvkey)
|
||||||
//configuration phase completed here
|
//configuration phase completed here
|
||||||
|
@ -175,14 +177,18 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
|
||||||
}
|
}
|
||||||
|
|
||||||
if networkid := ctx.GlobalString(SwarmNetworkIdFlag.Name); networkid != "" {
|
if networkid := ctx.GlobalString(SwarmNetworkIdFlag.Name); networkid != "" {
|
||||||
if id, _ := strconv.Atoi(networkid); id != 0 {
|
id, err := strconv.ParseUint(networkid, 10, 64)
|
||||||
currentConfig.NetworkID = uint64(id)
|
if err != nil {
|
||||||
|
utils.Fatalf("invalid cli flag %s: %v", SwarmNetworkIdFlag.Name, err)
|
||||||
|
}
|
||||||
|
if id != 0 {
|
||||||
|
currentConfig.NetworkID = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
|
if ctx.GlobalIsSet(utils.DataDirFlag.Name) {
|
||||||
if datadir := ctx.GlobalString(utils.DataDirFlag.Name); datadir != "" {
|
if datadir := ctx.GlobalString(utils.DataDirFlag.Name); datadir != "" {
|
||||||
currentConfig.Path = datadir
|
currentConfig.Path = expandPath(datadir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,6 +213,9 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
|
||||||
currentConfig.SyncUpdateDelay = d
|
currentConfig.SyncUpdateDelay = d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// any value including 0 is acceptable
|
||||||
|
currentConfig.MaxStreamPeerServers = ctx.GlobalInt(SwarmMaxStreamPeerServersFlag.Name)
|
||||||
|
|
||||||
if ctx.GlobalIsSet(SwarmLightNodeEnabled.Name) {
|
if ctx.GlobalIsSet(SwarmLightNodeEnabled.Name) {
|
||||||
currentConfig.LightNodeEnabled = true
|
currentConfig.LightNodeEnabled = true
|
||||||
}
|
}
|
||||||
|
@ -226,6 +235,10 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
|
||||||
if len(ensAPIs) == 1 && ensAPIs[0] == "" {
|
if len(ensAPIs) == 1 && ensAPIs[0] == "" {
|
||||||
ensAPIs = nil
|
ensAPIs = nil
|
||||||
}
|
}
|
||||||
|
for i := range ensAPIs {
|
||||||
|
ensAPIs[i] = expandPath(ensAPIs[i])
|
||||||
|
}
|
||||||
|
|
||||||
currentConfig.EnsAPIs = ensAPIs
|
currentConfig.EnsAPIs = ensAPIs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,13 +275,17 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if networkid := os.Getenv(SWARM_ENV_NETWORK_ID); networkid != "" {
|
if networkid := os.Getenv(SWARM_ENV_NETWORK_ID); networkid != "" {
|
||||||
if id, _ := strconv.Atoi(networkid); id != 0 {
|
id, err := strconv.ParseUint(networkid, 10, 64)
|
||||||
currentConfig.NetworkID = uint64(id)
|
if err != nil {
|
||||||
|
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_NETWORK_ID, err)
|
||||||
|
}
|
||||||
|
if id != 0 {
|
||||||
|
currentConfig.NetworkID = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if datadir := os.Getenv(GETH_ENV_DATADIR); datadir != "" {
|
if datadir := os.Getenv(GETH_ENV_DATADIR); datadir != "" {
|
||||||
currentConfig.Path = datadir
|
currentConfig.Path = expandPath(datadir)
|
||||||
}
|
}
|
||||||
|
|
||||||
bzzport := os.Getenv(SWARM_ENV_PORT)
|
bzzport := os.Getenv(SWARM_ENV_PORT)
|
||||||
|
@ -281,33 +298,50 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if swapenable := os.Getenv(SWARM_ENV_SWAP_ENABLE); swapenable != "" {
|
if swapenable := os.Getenv(SWARM_ENV_SWAP_ENABLE); swapenable != "" {
|
||||||
if swap, err := strconv.ParseBool(swapenable); err != nil {
|
swap, err := strconv.ParseBool(swapenable)
|
||||||
currentConfig.SwapEnabled = swap
|
if err != nil {
|
||||||
|
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_SWAP_ENABLE, err)
|
||||||
}
|
}
|
||||||
|
currentConfig.SwapEnabled = swap
|
||||||
}
|
}
|
||||||
|
|
||||||
if syncdisable := os.Getenv(SWARM_ENV_SYNC_DISABLE); syncdisable != "" {
|
if syncdisable := os.Getenv(SWARM_ENV_SYNC_DISABLE); syncdisable != "" {
|
||||||
if sync, err := strconv.ParseBool(syncdisable); err != nil {
|
sync, err := strconv.ParseBool(syncdisable)
|
||||||
currentConfig.SyncEnabled = !sync
|
if err != nil {
|
||||||
|
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_SYNC_DISABLE, err)
|
||||||
}
|
}
|
||||||
|
currentConfig.SyncEnabled = !sync
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(SWARM_ENV_DELIVERY_SKIP_CHECK); v != "" {
|
if v := os.Getenv(SWARM_ENV_DELIVERY_SKIP_CHECK); v != "" {
|
||||||
if skipCheck, err := strconv.ParseBool(v); err != nil {
|
skipCheck, err := strconv.ParseBool(v)
|
||||||
|
if err != nil {
|
||||||
currentConfig.DeliverySkipCheck = skipCheck
|
currentConfig.DeliverySkipCheck = skipCheck
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv(SWARM_ENV_SYNC_UPDATE_DELAY); v != "" {
|
if v := os.Getenv(SWARM_ENV_SYNC_UPDATE_DELAY); v != "" {
|
||||||
if d, err := time.ParseDuration(v); err != nil {
|
d, err := time.ParseDuration(v)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_SYNC_UPDATE_DELAY, err)
|
||||||
|
}
|
||||||
currentConfig.SyncUpdateDelay = d
|
currentConfig.SyncUpdateDelay = d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if max := os.Getenv(SWARM_ENV_MAX_STREAM_PEER_SERVERS); max != "" {
|
||||||
|
m, err := strconv.Atoi(max)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_MAX_STREAM_PEER_SERVERS, err)
|
||||||
|
}
|
||||||
|
currentConfig.MaxStreamPeerServers = m
|
||||||
}
|
}
|
||||||
|
|
||||||
if lne := os.Getenv(SWARM_ENV_LIGHT_NODE_ENABLE); lne != "" {
|
if lne := os.Getenv(SWARM_ENV_LIGHT_NODE_ENABLE); lne != "" {
|
||||||
if lightnode, err := strconv.ParseBool(lne); err != nil {
|
lightnode, err := strconv.ParseBool(lne)
|
||||||
currentConfig.LightNodeEnabled = lightnode
|
if err != nil {
|
||||||
|
utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_LIGHT_NODE_ENABLE, err)
|
||||||
}
|
}
|
||||||
|
currentConfig.LightNodeEnabled = lightnode
|
||||||
}
|
}
|
||||||
|
|
||||||
if swapapi := os.Getenv(SWARM_ENV_SWAP_API); swapapi != "" {
|
if swapapi := os.Getenv(SWARM_ENV_SWAP_API); swapapi != "" {
|
||||||
|
|
|
@ -29,6 +29,48 @@ import (
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var dbCommand = cli.Command{
|
||||||
|
Name: "db",
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Usage: "manage the local chunk database",
|
||||||
|
ArgsUsage: "db COMMAND",
|
||||||
|
Description: "Manage the local chunk database",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Action: dbExport,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "export",
|
||||||
|
Usage: "export a local chunk database as a tar archive (use - to send to stdout)",
|
||||||
|
ArgsUsage: "<chunkdb> <file>",
|
||||||
|
Description: `
|
||||||
|
Export a local chunk database as a tar archive (use - to send to stdout).
|
||||||
|
|
||||||
|
swarm db export ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
|
||||||
|
|
||||||
|
The export may be quite large, consider piping the output through the Unix
|
||||||
|
pv(1) tool to get a progress bar:
|
||||||
|
|
||||||
|
swarm db export ~/.ethereum/swarm/bzz-KEY/chunks - | pv > chunks.tar
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: dbImport,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "import",
|
||||||
|
Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
|
||||||
|
ArgsUsage: "<chunkdb> <file>",
|
||||||
|
Description: `Import chunks from a tar archive into a local chunk database (use - to read from stdin).
|
||||||
|
|
||||||
|
swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
|
||||||
|
|
||||||
|
The import may be quite large, consider piping the input through the Unix
|
||||||
|
pv(1) tool to get a progress bar:
|
||||||
|
|
||||||
|
pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks -`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func dbExport(ctx *cli.Context) {
|
func dbExport(ctx *cli.Context) {
|
||||||
args := ctx.Args()
|
args := ctx.Args()
|
||||||
if len(args) != 3 {
|
if len(args) != 3 {
|
||||||
|
@ -93,21 +135,6 @@ func dbImport(ctx *cli.Context) {
|
||||||
log.Info(fmt.Sprintf("successfully imported %d chunks", count))
|
log.Info(fmt.Sprintf("successfully imported %d chunks", count))
|
||||||
}
|
}
|
||||||
|
|
||||||
func dbClean(ctx *cli.Context) {
|
|
||||||
args := ctx.Args()
|
|
||||||
if len(args) != 2 {
|
|
||||||
utils.Fatalf("invalid arguments, please specify <chunkdb> (path to a local chunk database) and the base key")
|
|
||||||
}
|
|
||||||
|
|
||||||
store, err := openLDBStore(args[0], common.Hex2Bytes(args[1]))
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("error opening local chunk database: %s", err)
|
|
||||||
}
|
|
||||||
defer store.Close()
|
|
||||||
|
|
||||||
store.Cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
func openLDBStore(path string, basekey []byte) (*storage.LDBStore, error) {
|
func openLDBStore(path string, basekey []byte) (*storage.LDBStore, error) {
|
||||||
if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
|
if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
|
||||||
return nil, fmt.Errorf("invalid chunkdb path: %s", err)
|
return nil, fmt.Errorf("invalid chunkdb path: %s", err)
|
||||||
|
|
|
@ -28,6 +28,15 @@ import (
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var downloadCommand = cli.Command{
|
||||||
|
Action: download,
|
||||||
|
Name: "down",
|
||||||
|
Flags: []cli.Flag{SwarmRecursiveFlag, SwarmAccessPasswordFlag},
|
||||||
|
Usage: "downloads a swarm manifest or a file inside a manifest",
|
||||||
|
ArgsUsage: " <uri> [<dir>]",
|
||||||
|
Description: `Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.`,
|
||||||
|
}
|
||||||
|
|
||||||
func download(ctx *cli.Context) {
|
func download(ctx *cli.Context) {
|
||||||
log.Debug("downloading content using swarm down")
|
log.Debug("downloading content using swarm down")
|
||||||
args := ctx.Args()
|
args := ctx.Args()
|
||||||
|
|
|
@ -19,15 +19,15 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/rand"
|
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/swarm"
|
"github.com/ethereum/go-ethereum/swarm"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestCLISwarmExportImport perform the following test:
|
// TestCLISwarmExportImport perform the following test:
|
||||||
|
@ -38,14 +38,18 @@ import (
|
||||||
// 5. imports the exported datastore
|
// 5. imports the exported datastore
|
||||||
// 6. fetches the uploaded random file from the second node
|
// 6. fetches the uploaded random file from the second node
|
||||||
func TestCLISwarmExportImport(t *testing.T) {
|
func TestCLISwarmExportImport(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
cluster := newTestCluster(t, 1)
|
cluster := newTestCluster(t, 1)
|
||||||
|
|
||||||
// generate random 10mb file
|
// generate random 10mb file
|
||||||
f, cleanup := generateRandomFile(t, 10000000)
|
content := testutil.RandomBytes(1, 10000000)
|
||||||
defer cleanup()
|
fileName := testutil.TempFileWithContent(t, string(content))
|
||||||
|
defer os.Remove(fileName)
|
||||||
|
|
||||||
// upload the file with 'swarm up' and expect a hash
|
// upload the file with 'swarm up' and expect a hash
|
||||||
up := runSwarm(t, "--bzzapi", cluster.Nodes[0].URL, "up", f.Name())
|
up := runSwarm(t, "--bzzapi", cluster.Nodes[0].URL, "up", fileName)
|
||||||
_, matches := up.ExpectRegexp(`[a-f\d]{64}`)
|
_, matches := up.ExpectRegexp(`[a-f\d]{64}`)
|
||||||
up.ExpectExit()
|
up.ExpectExit()
|
||||||
hash := matches[0]
|
hash := matches[0]
|
||||||
|
@ -92,7 +96,7 @@ func TestCLISwarmExportImport(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare downloaded file with the generated random file
|
// compare downloaded file with the generated random file
|
||||||
mustEqualFiles(t, f, res.Body)
|
mustEqualFiles(t, bytes.NewReader(content), res.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustEqualFiles(t *testing.T, up io.Reader, down io.Reader) {
|
func mustEqualFiles(t *testing.T, up io.Reader, down io.Reader) {
|
||||||
|
@ -113,27 +117,3 @@ func mustEqualFiles(t *testing.T, up io.Reader, down io.Reader) {
|
||||||
t.Fatalf("downloaded imported file md5=%x (length %v) is not the same as the generated one mp5=%x (length %v)", downHash, downLen, upHash, upLen)
|
t.Fatalf("downloaded imported file md5=%x (length %v) is not the same as the generated one mp5=%x (length %v)", downHash, downLen, upHash, upLen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateRandomFile(t *testing.T, size int) (f *os.File, teardown func()) {
|
|
||||||
// create a tmp file
|
|
||||||
tmp, err := ioutil.TempFile("", "swarm-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// callback for tmp file cleanup
|
|
||||||
teardown = func() {
|
|
||||||
tmp.Close()
|
|
||||||
os.Remove(tmp.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
// write 10mb random data to file
|
|
||||||
buf := make([]byte, 10000000)
|
|
||||||
_, err = rand.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
ioutil.WriteFile(tmp.Name(), buf, 0755)
|
|
||||||
|
|
||||||
return tmp, teardown
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,234 @@
|
||||||
|
// Copyright 2016 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Command feed allows the user to create and update signed Swarm feeds
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
|
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
||||||
|
"gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var feedCommand = cli.Command{
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "feed",
|
||||||
|
Usage: "(Advanced) Create and update Swarm Feeds",
|
||||||
|
ArgsUsage: "<create|update|info>",
|
||||||
|
Description: "Works with Swarm Feeds",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Action: feedCreateManifest,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "create",
|
||||||
|
Usage: "creates and publishes a new feed manifest",
|
||||||
|
Description: `creates and publishes a new feed manifest pointing to a specified user's updates about a particular topic.
|
||||||
|
The feed topic can be built in the following ways:
|
||||||
|
* use --topic to set the topic to an arbitrary binary hex string.
|
||||||
|
* use --name to set the topic to a human-readable name.
|
||||||
|
For example --name could be set to "profile-picture", meaning this feed allows to get this user's current profile picture.
|
||||||
|
* use both --topic and --name to create named subtopics.
|
||||||
|
For example, --topic could be set to an Ethereum contract address and --name could be set to "comments", meaning
|
||||||
|
this feed tracks a discussion about that contract.
|
||||||
|
The --user flag allows to have this manifest refer to a user other than yourself. If not specified,
|
||||||
|
it will then default to your local account (--bzzaccount)`,
|
||||||
|
Flags: []cli.Flag{SwarmFeedNameFlag, SwarmFeedTopicFlag, SwarmFeedUserFlag},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: feedUpdate,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "update",
|
||||||
|
Usage: "updates the content of an existing Swarm Feed",
|
||||||
|
ArgsUsage: "<0x Hex data>",
|
||||||
|
Description: `publishes a new update on the specified topic
|
||||||
|
The feed topic can be built in the following ways:
|
||||||
|
* use --topic to set the topic to an arbitrary binary hex string.
|
||||||
|
* use --name to set the topic to a human-readable name.
|
||||||
|
For example --name could be set to "profile-picture", meaning this feed allows to get this user's current profile picture.
|
||||||
|
* use both --topic and --name to create named subtopics.
|
||||||
|
For example, --topic could be set to an Ethereum contract address and --name could be set to "comments", meaning
|
||||||
|
this feed tracks a discussion about that contract.
|
||||||
|
|
||||||
|
If you have a manifest, you can specify it with --manifest to refer to the feed,
|
||||||
|
instead of using --topic / --name
|
||||||
|
`,
|
||||||
|
Flags: []cli.Flag{SwarmFeedManifestFlag, SwarmFeedNameFlag, SwarmFeedTopicFlag},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: feedInfo,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "info",
|
||||||
|
Usage: "obtains information about an existing Swarm feed",
|
||||||
|
Description: `obtains information about an existing Swarm feed
|
||||||
|
The topic can be specified directly with the --topic flag as an hex string
|
||||||
|
If no topic is specified, the default topic (zero) will be used
|
||||||
|
The --name flag can be used to specify subtopics with a specific name.
|
||||||
|
The --user flag allows to refer to a user other than yourself. If not specified,
|
||||||
|
it will then default to your local account (--bzzaccount)
|
||||||
|
If you have a manifest, you can specify it with --manifest instead of --topic / --name / ---user
|
||||||
|
to refer to the feed`,
|
||||||
|
Flags: []cli.Flag{SwarmFeedManifestFlag, SwarmFeedNameFlag, SwarmFeedTopicFlag, SwarmFeedUserFlag},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGenericSigner(ctx *cli.Context) feed.Signer {
|
||||||
|
return feed.NewGenericSigner(getPrivKey(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTopic(ctx *cli.Context) (topic feed.Topic) {
|
||||||
|
var name = ctx.String(SwarmFeedNameFlag.Name)
|
||||||
|
var relatedTopic = ctx.String(SwarmFeedTopicFlag.Name)
|
||||||
|
var relatedTopicBytes []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if relatedTopic != "" {
|
||||||
|
relatedTopicBytes, err = hexutil.Decode(relatedTopic)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error parsing topic: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
topic, err = feed.NewTopic(name, relatedTopicBytes)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error parsing topic: %s", err)
|
||||||
|
}
|
||||||
|
return topic
|
||||||
|
}
|
||||||
|
|
||||||
|
// swarm feed create <frequency> [--name <name>] [--data <0x Hexdata> [--multihash=false]]
|
||||||
|
// swarm feed update <Manifest Address or ENS domain> <0x Hexdata> [--multihash=false]
|
||||||
|
// swarm feed info <Manifest Address or ENS domain>
|
||||||
|
|
||||||
|
func feedCreateManifest(ctx *cli.Context) {
|
||||||
|
var (
|
||||||
|
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||||
|
client = swarm.NewClient(bzzapi)
|
||||||
|
)
|
||||||
|
|
||||||
|
newFeedUpdateRequest := feed.NewFirstRequest(getTopic(ctx))
|
||||||
|
newFeedUpdateRequest.Feed.User = feedGetUser(ctx)
|
||||||
|
|
||||||
|
manifestAddress, err := client.CreateFeedWithManifest(newFeedUpdateRequest)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error creating feed manifest: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(manifestAddress) // output manifest address to the user in a single line (useful for other commands to pick up)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func feedUpdate(ctx *cli.Context) {
|
||||||
|
args := ctx.Args()
|
||||||
|
|
||||||
|
var (
|
||||||
|
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||||
|
client = swarm.NewClient(bzzapi)
|
||||||
|
manifestAddressOrDomain = ctx.String(SwarmFeedManifestFlag.Name)
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
fmt.Println("Incorrect number of arguments")
|
||||||
|
cli.ShowCommandHelpAndExit(ctx, "update", 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
signer := NewGenericSigner(ctx)
|
||||||
|
|
||||||
|
data, err := hexutil.Decode(args[0])
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error parsing data: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateRequest *feed.Request
|
||||||
|
var query *feed.Query
|
||||||
|
|
||||||
|
if manifestAddressOrDomain == "" {
|
||||||
|
query = new(feed.Query)
|
||||||
|
query.User = signer.Address()
|
||||||
|
query.Topic = getTopic(ctx)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve a feed update request
|
||||||
|
updateRequest, err = client.GetFeedRequest(query, manifestAddressOrDomain)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error retrieving feed status: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the new data
|
||||||
|
updateRequest.SetData(data)
|
||||||
|
|
||||||
|
// sign update
|
||||||
|
if err = updateRequest.Sign(signer); err != nil {
|
||||||
|
utils.Fatalf("Error signing feed update: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// post update
|
||||||
|
err = client.UpdateFeed(updateRequest)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error updating feed: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func feedInfo(ctx *cli.Context) {
|
||||||
|
var (
|
||||||
|
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||||
|
client = swarm.NewClient(bzzapi)
|
||||||
|
manifestAddressOrDomain = ctx.String(SwarmFeedManifestFlag.Name)
|
||||||
|
)
|
||||||
|
|
||||||
|
var query *feed.Query
|
||||||
|
if manifestAddressOrDomain == "" {
|
||||||
|
query = new(feed.Query)
|
||||||
|
query.Topic = getTopic(ctx)
|
||||||
|
query.User = feedGetUser(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata, err := client.GetFeedRequest(query, manifestAddressOrDomain)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error retrieving feed metadata: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
encodedMetadata, err := metadata.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("Error encoding metadata to JSON for display:%s", err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(encodedMetadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
func feedGetUser(ctx *cli.Context) common.Address {
|
||||||
|
var user = ctx.String(SwarmFeedUserFlag.Name)
|
||||||
|
if user != "" {
|
||||||
|
return common.HexToAddress(user)
|
||||||
|
}
|
||||||
|
pk := getPrivKey(ctx)
|
||||||
|
if pk == nil {
|
||||||
|
utils.Fatalf("Cannot read private key. Must specify --user or --bzzaccount")
|
||||||
|
}
|
||||||
|
return crypto.PubkeyToAddress(pk.PublicKey)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,167 @@
|
||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||||
|
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCLIFeedUpdate(t *testing.T) {
|
||||||
|
|
||||||
|
srv := swarmhttp.NewTestSwarmServer(t, func(api *api.API) swarmhttp.TestServer {
|
||||||
|
return swarmhttp.NewServer(api, "")
|
||||||
|
}, nil)
|
||||||
|
log.Info("starting a test swarm server")
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
// create a private key file for signing
|
||||||
|
|
||||||
|
privkeyHex := "0000000000000000000000000000000000000000000000000000000000001979"
|
||||||
|
privKey, _ := crypto.HexToECDSA(privkeyHex)
|
||||||
|
address := crypto.PubkeyToAddress(privKey.PublicKey)
|
||||||
|
|
||||||
|
pkFileName := testutil.TempFileWithContent(t, privkeyHex)
|
||||||
|
defer os.Remove(pkFileName)
|
||||||
|
|
||||||
|
// compose a topic. We'll be doing quotes about Miguel de Cervantes
|
||||||
|
var topic feed.Topic
|
||||||
|
subject := []byte("Miguel de Cervantes")
|
||||||
|
copy(topic[:], subject[:])
|
||||||
|
name := "quotes"
|
||||||
|
|
||||||
|
// prepare some data for the update
|
||||||
|
data := []byte("En boca cerrada no entran moscas")
|
||||||
|
hexData := hexutil.Encode(data)
|
||||||
|
|
||||||
|
flags := []string{
|
||||||
|
"--bzzapi", srv.URL,
|
||||||
|
"--bzzaccount", pkFileName,
|
||||||
|
"feed", "update",
|
||||||
|
"--topic", topic.Hex(),
|
||||||
|
"--name", name,
|
||||||
|
hexData}
|
||||||
|
|
||||||
|
// create an update and expect an exit without errors
|
||||||
|
log.Info(fmt.Sprintf("updating a feed with 'swarm feed update'"))
|
||||||
|
cmd := runSwarm(t, flags...)
|
||||||
|
cmd.ExpectExit()
|
||||||
|
|
||||||
|
// now try to get the update using the client
|
||||||
|
client := swarm.NewClient(srv.URL)
|
||||||
|
|
||||||
|
// build the same topic as before, this time
|
||||||
|
// we use NewTopic to create a topic automatically.
|
||||||
|
topic, err := feed.NewTopic(name, subject)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed configures whose updates we will be looking up.
|
||||||
|
fd := feed.Feed{
|
||||||
|
Topic: topic,
|
||||||
|
User: address,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a query to get the latest update
|
||||||
|
query := feed.NewQueryLatest(&fd, lookup.NoClue)
|
||||||
|
|
||||||
|
// retrieve content!
|
||||||
|
reader, err := client.QueryFeed(query, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieved, err := ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check we retrieved the sent information
|
||||||
|
if !bytes.Equal(data, retrieved) {
|
||||||
|
t.Fatalf("Received %s, expected %s", retrieved, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now retrieve info for the next update
|
||||||
|
flags = []string{
|
||||||
|
"--bzzapi", srv.URL,
|
||||||
|
"feed", "info",
|
||||||
|
"--topic", topic.Hex(),
|
||||||
|
"--user", address.Hex(),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(fmt.Sprintf("getting feed info with 'swarm feed info'"))
|
||||||
|
cmd = runSwarm(t, flags...)
|
||||||
|
_, matches := cmd.ExpectRegexp(`.*`) // regex hack to extract stdout
|
||||||
|
cmd.ExpectExit()
|
||||||
|
|
||||||
|
// verify we can deserialize the result as a valid JSON
|
||||||
|
var request feed.Request
|
||||||
|
err = json.Unmarshal([]byte(matches[0]), &request)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the retrieved feed is the same
|
||||||
|
if request.Feed != fd {
|
||||||
|
t.Fatalf("Expected feed to be: %s, got %s", fd, request.Feed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test publishing a manifest
|
||||||
|
flags = []string{
|
||||||
|
"--bzzapi", srv.URL,
|
||||||
|
"--bzzaccount", pkFileName,
|
||||||
|
"feed", "create",
|
||||||
|
"--topic", topic.Hex(),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(fmt.Sprintf("Publishing manifest with 'swarm feed create'"))
|
||||||
|
cmd = runSwarm(t, flags...)
|
||||||
|
_, matches = cmd.ExpectRegexp(`[a-f\d]{64}`) // regex hack to extract stdout
|
||||||
|
cmd.ExpectExit()
|
||||||
|
|
||||||
|
manifestAddress := matches[0] // read the received feed manifest
|
||||||
|
|
||||||
|
// now attempt to lookup the latest update using a manifest instead
|
||||||
|
reader, err = client.QueryFeed(nil, manifestAddress)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
retrieved, err = ioutil.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(data, retrieved) {
|
||||||
|
t.Fatalf("Received %s, expected %s", retrieved, data)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Command feed allows the user to create and update signed Swarm feeds
|
||||||
|
package main
|
||||||
|
|
||||||
|
import cli "gopkg.in/urfave/cli.v1"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ChequebookAddrFlag = cli.StringFlag{
|
||||||
|
Name: "chequebook",
|
||||||
|
Usage: "chequebook contract address",
|
||||||
|
EnvVar: SWARM_ENV_CHEQUEBOOK_ADDR,
|
||||||
|
}
|
||||||
|
SwarmAccountFlag = cli.StringFlag{
|
||||||
|
Name: "bzzaccount",
|
||||||
|
Usage: "Swarm account key file",
|
||||||
|
EnvVar: SWARM_ENV_ACCOUNT,
|
||||||
|
}
|
||||||
|
SwarmListenAddrFlag = cli.StringFlag{
|
||||||
|
Name: "httpaddr",
|
||||||
|
Usage: "Swarm HTTP API listening interface",
|
||||||
|
EnvVar: SWARM_ENV_LISTEN_ADDR,
|
||||||
|
}
|
||||||
|
SwarmPortFlag = cli.StringFlag{
|
||||||
|
Name: "bzzport",
|
||||||
|
Usage: "Swarm local http api port",
|
||||||
|
EnvVar: SWARM_ENV_PORT,
|
||||||
|
}
|
||||||
|
SwarmNetworkIdFlag = cli.IntFlag{
|
||||||
|
Name: "bzznetworkid",
|
||||||
|
Usage: "Network identifier (integer, default 3=swarm testnet)",
|
||||||
|
EnvVar: SWARM_ENV_NETWORK_ID,
|
||||||
|
}
|
||||||
|
SwarmSwapEnabledFlag = cli.BoolFlag{
|
||||||
|
Name: "swap",
|
||||||
|
Usage: "Swarm SWAP enabled (default false)",
|
||||||
|
EnvVar: SWARM_ENV_SWAP_ENABLE,
|
||||||
|
}
|
||||||
|
SwarmSwapAPIFlag = cli.StringFlag{
|
||||||
|
Name: "swap-api",
|
||||||
|
Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
|
||||||
|
EnvVar: SWARM_ENV_SWAP_API,
|
||||||
|
}
|
||||||
|
SwarmSyncDisabledFlag = cli.BoolTFlag{
|
||||||
|
Name: "nosync",
|
||||||
|
Usage: "Disable swarm syncing",
|
||||||
|
EnvVar: SWARM_ENV_SYNC_DISABLE,
|
||||||
|
}
|
||||||
|
SwarmSyncUpdateDelay = cli.DurationFlag{
|
||||||
|
Name: "sync-update-delay",
|
||||||
|
Usage: "Duration for sync subscriptions update after no new peers are added (default 15s)",
|
||||||
|
EnvVar: SWARM_ENV_SYNC_UPDATE_DELAY,
|
||||||
|
}
|
||||||
|
SwarmMaxStreamPeerServersFlag = cli.IntFlag{
|
||||||
|
Name: "max-stream-peer-servers",
|
||||||
|
Usage: "Limit of Stream peer servers, 0 denotes unlimited",
|
||||||
|
EnvVar: SWARM_ENV_MAX_STREAM_PEER_SERVERS,
|
||||||
|
Value: 10000, // A very large default value is possible as stream servers have very small memory footprint
|
||||||
|
}
|
||||||
|
SwarmLightNodeEnabled = cli.BoolFlag{
|
||||||
|
Name: "lightnode",
|
||||||
|
Usage: "Enable Swarm LightNode (default false)",
|
||||||
|
EnvVar: SWARM_ENV_LIGHT_NODE_ENABLE,
|
||||||
|
}
|
||||||
|
SwarmDeliverySkipCheckFlag = cli.BoolFlag{
|
||||||
|
Name: "delivery-skip-check",
|
||||||
|
Usage: "Skip chunk delivery check (default false)",
|
||||||
|
EnvVar: SWARM_ENV_DELIVERY_SKIP_CHECK,
|
||||||
|
}
|
||||||
|
EnsAPIFlag = cli.StringSliceFlag{
|
||||||
|
Name: "ens-api",
|
||||||
|
Usage: "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url",
|
||||||
|
EnvVar: SWARM_ENV_ENS_API,
|
||||||
|
}
|
||||||
|
SwarmApiFlag = cli.StringFlag{
|
||||||
|
Name: "bzzapi",
|
||||||
|
Usage: "Specifies the Swarm HTTP endpoint to connect to",
|
||||||
|
Value: "http://127.0.0.1:8500",
|
||||||
|
}
|
||||||
|
SwarmRecursiveFlag = cli.BoolFlag{
|
||||||
|
Name: "recursive",
|
||||||
|
Usage: "Upload directories recursively",
|
||||||
|
}
|
||||||
|
SwarmWantManifestFlag = cli.BoolTFlag{
|
||||||
|
Name: "manifest",
|
||||||
|
Usage: "Automatic manifest upload (default true)",
|
||||||
|
}
|
||||||
|
SwarmUploadDefaultPath = cli.StringFlag{
|
||||||
|
Name: "defaultpath",
|
||||||
|
Usage: "path to file served for empty url path (none)",
|
||||||
|
}
|
||||||
|
SwarmAccessGrantKeyFlag = cli.StringFlag{
|
||||||
|
Name: "grant-key",
|
||||||
|
Usage: "grants a given public key access to an ACT",
|
||||||
|
}
|
||||||
|
SwarmAccessGrantKeysFlag = cli.StringFlag{
|
||||||
|
Name: "grant-keys",
|
||||||
|
Usage: "grants a given list of public keys in the following file (separated by line breaks) access to an ACT",
|
||||||
|
}
|
||||||
|
SwarmUpFromStdinFlag = cli.BoolFlag{
|
||||||
|
Name: "stdin",
|
||||||
|
Usage: "reads data to be uploaded from stdin",
|
||||||
|
}
|
||||||
|
SwarmUploadMimeType = cli.StringFlag{
|
||||||
|
Name: "mime",
|
||||||
|
Usage: "Manually specify MIME type",
|
||||||
|
}
|
||||||
|
SwarmEncryptedFlag = cli.BoolFlag{
|
||||||
|
Name: "encrypt",
|
||||||
|
Usage: "use encrypted upload",
|
||||||
|
}
|
||||||
|
SwarmAccessPasswordFlag = cli.StringFlag{
|
||||||
|
Name: "password",
|
||||||
|
Usage: "Password",
|
||||||
|
EnvVar: SWARM_ACCESS_PASSWORD,
|
||||||
|
}
|
||||||
|
SwarmDryRunFlag = cli.BoolFlag{
|
||||||
|
Name: "dry-run",
|
||||||
|
Usage: "dry-run",
|
||||||
|
}
|
||||||
|
CorsStringFlag = cli.StringFlag{
|
||||||
|
Name: "corsdomain",
|
||||||
|
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
||||||
|
EnvVar: SWARM_ENV_CORS,
|
||||||
|
}
|
||||||
|
SwarmStorePath = cli.StringFlag{
|
||||||
|
Name: "store.path",
|
||||||
|
Usage: "Path to leveldb chunk DB (default <$GETH_ENV_DIR>/swarm/bzz-<$BZZ_KEY>/chunks)",
|
||||||
|
EnvVar: SWARM_ENV_STORE_PATH,
|
||||||
|
}
|
||||||
|
SwarmStoreCapacity = cli.Uint64Flag{
|
||||||
|
Name: "store.size",
|
||||||
|
Usage: "Number of chunks (5M is roughly 20-25GB) (default 5000000)",
|
||||||
|
EnvVar: SWARM_ENV_STORE_CAPACITY,
|
||||||
|
}
|
||||||
|
SwarmStoreCacheCapacity = cli.UintFlag{
|
||||||
|
Name: "store.cache.size",
|
||||||
|
Usage: "Number of recent chunks cached in memory (default 5000)",
|
||||||
|
EnvVar: SWARM_ENV_STORE_CACHE_CAPACITY,
|
||||||
|
}
|
||||||
|
SwarmCompressedFlag = cli.BoolFlag{
|
||||||
|
Name: "compressed",
|
||||||
|
Usage: "Prints encryption keys in compressed form",
|
||||||
|
}
|
||||||
|
SwarmFeedNameFlag = cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Usage: "User-defined name for the new feed, limited to 32 characters. If combined with topic, it will refer to a subtopic with this name",
|
||||||
|
}
|
||||||
|
SwarmFeedTopicFlag = cli.StringFlag{
|
||||||
|
Name: "topic",
|
||||||
|
Usage: "User-defined topic this feed is tracking, hex encoded. Limited to 64 hexadecimal characters",
|
||||||
|
}
|
||||||
|
SwarmFeedDataOnCreateFlag = cli.StringFlag{
|
||||||
|
Name: "data",
|
||||||
|
Usage: "Initializes the feed with the given hex-encoded data. Data must be prefixed by 0x",
|
||||||
|
}
|
||||||
|
SwarmFeedManifestFlag = cli.StringFlag{
|
||||||
|
Name: "manifest",
|
||||||
|
Usage: "Refers to the feed through a manifest",
|
||||||
|
}
|
||||||
|
SwarmFeedUserFlag = cli.StringFlag{
|
||||||
|
Name: "user",
|
||||||
|
Usage: "Indicates the user who updates the feed",
|
||||||
|
}
|
||||||
|
)
|
|
@ -30,6 +30,43 @@ import (
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var fsCommand = cli.Command{
|
||||||
|
Name: "fs",
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Usage: "perform FUSE operations",
|
||||||
|
ArgsUsage: "fs COMMAND",
|
||||||
|
Description: "Performs FUSE operations by mounting/unmounting/listing mount points. This assumes you already have a Swarm node running locally. For all operation you must reference the correct path to bzzd.ipc in order to communicate with the node",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Action: mount,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "mount",
|
||||||
|
Flags: []cli.Flag{utils.IPCPathFlag},
|
||||||
|
Usage: "mount a swarm hash to a mount point",
|
||||||
|
ArgsUsage: "swarm fs mount --ipcpath <path to bzzd.ipc> <manifest hash> <mount point>",
|
||||||
|
Description: "Mounts a Swarm manifest hash to a given mount point. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: unmount,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "unmount",
|
||||||
|
Flags: []cli.Flag{utils.IPCPathFlag},
|
||||||
|
Usage: "unmount a swarmfs mount",
|
||||||
|
ArgsUsage: "swarm fs unmount --ipcpath <path to bzzd.ipc> <mount point>",
|
||||||
|
Description: "Unmounts a swarmfs mount residing at <mount point>. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: listMounts,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "list",
|
||||||
|
Flags: []cli.Flag{utils.IPCPathFlag},
|
||||||
|
Usage: "list swarmfs mounts",
|
||||||
|
ArgsUsage: "swarm fs list --ipcpath <path to bzzd.ipc>",
|
||||||
|
Description: "Lists all mounted swarmfs volumes. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func mount(cliContext *cli.Context) {
|
func mount(cliContext *cli.Context) {
|
||||||
args := cliContext.Args()
|
args := cliContext.Args()
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
|
|
|
@ -80,6 +80,9 @@ func TestCLISwarmFs(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
dirPath2, err := createDirInDir(dirPath, "AnotherTestSubDir")
|
dirPath2, err := createDirInDir(dirPath, "AnotherTestSubDir")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
dummyContent := "somerandomtestcontentthatshouldbeasserted"
|
dummyContent := "somerandomtestcontentthatshouldbeasserted"
|
||||||
dirs := []string{
|
dirs := []string{
|
||||||
|
|
|
@ -27,6 +27,15 @@ import (
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var hashCommand = cli.Command{
|
||||||
|
Action: hash,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "hash",
|
||||||
|
Usage: "print the swarm hash of a file or directory",
|
||||||
|
ArgsUsage: "<file>",
|
||||||
|
Description: "Prints the swarm hash of file or directory",
|
||||||
|
}
|
||||||
|
|
||||||
func hash(ctx *cli.Context) {
|
func hash(ctx *cli.Context) {
|
||||||
args := ctx.Args()
|
args := ctx.Args()
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
|
|
|
@ -27,6 +27,15 @@ import (
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var listCommand = cli.Command{
|
||||||
|
Action: list,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "ls",
|
||||||
|
Usage: "list files and directories contained in a manifest",
|
||||||
|
ArgsUsage: "<manifest> [<prefix>]",
|
||||||
|
Description: "Lists files and directories contained in a manifest",
|
||||||
|
}
|
||||||
|
|
||||||
func list(ctx *cli.Context) {
|
func list(ctx *cli.Context) {
|
||||||
args := ctx.Args()
|
args := ctx.Args()
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/swarm"
|
"github.com/ethereum/go-ethereum/swarm"
|
||||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||||
swarmmetrics "github.com/ethereum/go-ethereum/swarm/metrics"
|
swarmmetrics "github.com/ethereum/go-ethereum/swarm/metrics"
|
||||||
|
@ -70,151 +70,6 @@ var (
|
||||||
gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ChequebookAddrFlag = cli.StringFlag{
|
|
||||||
Name: "chequebook",
|
|
||||||
Usage: "chequebook contract address",
|
|
||||||
EnvVar: SWARM_ENV_CHEQUEBOOK_ADDR,
|
|
||||||
}
|
|
||||||
SwarmAccountFlag = cli.StringFlag{
|
|
||||||
Name: "bzzaccount",
|
|
||||||
Usage: "Swarm account key file",
|
|
||||||
EnvVar: SWARM_ENV_ACCOUNT,
|
|
||||||
}
|
|
||||||
SwarmListenAddrFlag = cli.StringFlag{
|
|
||||||
Name: "httpaddr",
|
|
||||||
Usage: "Swarm HTTP API listening interface",
|
|
||||||
EnvVar: SWARM_ENV_LISTEN_ADDR,
|
|
||||||
}
|
|
||||||
SwarmPortFlag = cli.StringFlag{
|
|
||||||
Name: "bzzport",
|
|
||||||
Usage: "Swarm local http api port",
|
|
||||||
EnvVar: SWARM_ENV_PORT,
|
|
||||||
}
|
|
||||||
SwarmNetworkIdFlag = cli.IntFlag{
|
|
||||||
Name: "bzznetworkid",
|
|
||||||
Usage: "Network identifier (integer, default 3=swarm testnet)",
|
|
||||||
EnvVar: SWARM_ENV_NETWORK_ID,
|
|
||||||
}
|
|
||||||
SwarmSwapEnabledFlag = cli.BoolFlag{
|
|
||||||
Name: "swap",
|
|
||||||
Usage: "Swarm SWAP enabled (default false)",
|
|
||||||
EnvVar: SWARM_ENV_SWAP_ENABLE,
|
|
||||||
}
|
|
||||||
SwarmSwapAPIFlag = cli.StringFlag{
|
|
||||||
Name: "swap-api",
|
|
||||||
Usage: "URL of the Ethereum API provider to use to settle SWAP payments",
|
|
||||||
EnvVar: SWARM_ENV_SWAP_API,
|
|
||||||
}
|
|
||||||
SwarmSyncDisabledFlag = cli.BoolTFlag{
|
|
||||||
Name: "nosync",
|
|
||||||
Usage: "Disable swarm syncing",
|
|
||||||
EnvVar: SWARM_ENV_SYNC_DISABLE,
|
|
||||||
}
|
|
||||||
SwarmSyncUpdateDelay = cli.DurationFlag{
|
|
||||||
Name: "sync-update-delay",
|
|
||||||
Usage: "Duration for sync subscriptions update after no new peers are added (default 15s)",
|
|
||||||
EnvVar: SWARM_ENV_SYNC_UPDATE_DELAY,
|
|
||||||
}
|
|
||||||
SwarmLightNodeEnabled = cli.BoolFlag{
|
|
||||||
Name: "lightnode",
|
|
||||||
Usage: "Enable Swarm LightNode (default false)",
|
|
||||||
EnvVar: SWARM_ENV_LIGHT_NODE_ENABLE,
|
|
||||||
}
|
|
||||||
SwarmDeliverySkipCheckFlag = cli.BoolFlag{
|
|
||||||
Name: "delivery-skip-check",
|
|
||||||
Usage: "Skip chunk delivery check (default false)",
|
|
||||||
EnvVar: SWARM_ENV_DELIVERY_SKIP_CHECK,
|
|
||||||
}
|
|
||||||
EnsAPIFlag = cli.StringSliceFlag{
|
|
||||||
Name: "ens-api",
|
|
||||||
Usage: "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url",
|
|
||||||
EnvVar: SWARM_ENV_ENS_API,
|
|
||||||
}
|
|
||||||
SwarmApiFlag = cli.StringFlag{
|
|
||||||
Name: "bzzapi",
|
|
||||||
Usage: "Swarm HTTP endpoint",
|
|
||||||
Value: "http://127.0.0.1:8500",
|
|
||||||
}
|
|
||||||
SwarmRecursiveFlag = cli.BoolFlag{
|
|
||||||
Name: "recursive",
|
|
||||||
Usage: "Upload directories recursively",
|
|
||||||
}
|
|
||||||
SwarmWantManifestFlag = cli.BoolTFlag{
|
|
||||||
Name: "manifest",
|
|
||||||
Usage: "Automatic manifest upload (default true)",
|
|
||||||
}
|
|
||||||
SwarmUploadDefaultPath = cli.StringFlag{
|
|
||||||
Name: "defaultpath",
|
|
||||||
Usage: "path to file served for empty url path (none)",
|
|
||||||
}
|
|
||||||
SwarmAccessGrantKeyFlag = cli.StringFlag{
|
|
||||||
Name: "grant-key",
|
|
||||||
Usage: "grants a given public key access to an ACT",
|
|
||||||
}
|
|
||||||
SwarmAccessGrantKeysFlag = cli.StringFlag{
|
|
||||||
Name: "grant-keys",
|
|
||||||
Usage: "grants a given list of public keys in the following file (separated by line breaks) access to an ACT",
|
|
||||||
}
|
|
||||||
SwarmUpFromStdinFlag = cli.BoolFlag{
|
|
||||||
Name: "stdin",
|
|
||||||
Usage: "reads data to be uploaded from stdin",
|
|
||||||
}
|
|
||||||
SwarmUploadMimeType = cli.StringFlag{
|
|
||||||
Name: "mime",
|
|
||||||
Usage: "Manually specify MIME type",
|
|
||||||
}
|
|
||||||
SwarmEncryptedFlag = cli.BoolFlag{
|
|
||||||
Name: "encrypt",
|
|
||||||
Usage: "use encrypted upload",
|
|
||||||
}
|
|
||||||
SwarmAccessPasswordFlag = cli.StringFlag{
|
|
||||||
Name: "password",
|
|
||||||
Usage: "Password",
|
|
||||||
EnvVar: SWARM_ACCESS_PASSWORD,
|
|
||||||
}
|
|
||||||
SwarmDryRunFlag = cli.BoolFlag{
|
|
||||||
Name: "dry-run",
|
|
||||||
Usage: "dry-run",
|
|
||||||
}
|
|
||||||
CorsStringFlag = cli.StringFlag{
|
|
||||||
Name: "corsdomain",
|
|
||||||
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
|
||||||
EnvVar: SWARM_ENV_CORS,
|
|
||||||
}
|
|
||||||
SwarmStorePath = cli.StringFlag{
|
|
||||||
Name: "store.path",
|
|
||||||
Usage: "Path to leveldb chunk DB (default <$GETH_ENV_DIR>/swarm/bzz-<$BZZ_KEY>/chunks)",
|
|
||||||
EnvVar: SWARM_ENV_STORE_PATH,
|
|
||||||
}
|
|
||||||
SwarmStoreCapacity = cli.Uint64Flag{
|
|
||||||
Name: "store.size",
|
|
||||||
Usage: "Number of chunks (5M is roughly 20-25GB) (default 5000000)",
|
|
||||||
EnvVar: SWARM_ENV_STORE_CAPACITY,
|
|
||||||
}
|
|
||||||
SwarmStoreCacheCapacity = cli.UintFlag{
|
|
||||||
Name: "store.cache.size",
|
|
||||||
Usage: "Number of recent chunks cached in memory (default 5000)",
|
|
||||||
EnvVar: SWARM_ENV_STORE_CACHE_CAPACITY,
|
|
||||||
}
|
|
||||||
SwarmResourceMultihashFlag = cli.BoolFlag{
|
|
||||||
Name: "multihash",
|
|
||||||
Usage: "Determines how to interpret data for a resource update. If not present, data will be interpreted as raw, literal data that will be included in the resource",
|
|
||||||
}
|
|
||||||
SwarmResourceNameFlag = cli.StringFlag{
|
|
||||||
Name: "name",
|
|
||||||
Usage: "User-defined name for the new resource",
|
|
||||||
}
|
|
||||||
SwarmResourceDataOnCreateFlag = cli.StringFlag{
|
|
||||||
Name: "data",
|
|
||||||
Usage: "Initializes the resource with the given hex-encoded data. Data must be prefixed by 0x",
|
|
||||||
}
|
|
||||||
SwarmCompressedFlag = cli.BoolFlag{
|
|
||||||
Name: "compressed",
|
|
||||||
Usage: "Prints encryption keys in compressed form",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
//declare a few constant error messages, useful for later error check comparisons in test
|
//declare a few constant error messages, useful for later error check comparisons in test
|
||||||
var (
|
var (
|
||||||
SWARM_ERR_NO_BZZACCOUNT = "bzzaccount option is required but not set; check your config file, command line or environment variables"
|
SWARM_ERR_NO_BZZACCOUNT = "bzzaccount option is required but not set; check your config file, command line or environment variables"
|
||||||
|
@ -242,12 +97,12 @@ func init() {
|
||||||
utils.ListenPortFlag.Value = 30399
|
utils.ListenPortFlag.Value = 30399
|
||||||
}
|
}
|
||||||
|
|
||||||
var app = utils.NewApp(gitCommit, "Ethereum Swarm")
|
var app = utils.NewApp("", "Ethereum Swarm")
|
||||||
|
|
||||||
// This init function creates the cli.App.
|
// This init function creates the cli.App.
|
||||||
func init() {
|
func init() {
|
||||||
app.Action = bzzd
|
app.Action = bzzd
|
||||||
app.HideVersion = true // we have a command to print the version
|
app.Version = sv.ArchiveVersion(gitCommit)
|
||||||
app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
|
app.Copyright = "Copyright 2013-2016 The go-ethereum Authors"
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
{
|
{
|
||||||
|
@ -265,249 +120,24 @@ func init() {
|
||||||
Usage: "Print public key information",
|
Usage: "Print public key information",
|
||||||
Description: "The output of this command is supposed to be machine-readable",
|
Description: "The output of this command is supposed to be machine-readable",
|
||||||
},
|
},
|
||||||
{
|
// See upload.go
|
||||||
Action: upload,
|
upCommand,
|
||||||
CustomHelpTemplate: helpTemplate,
|
// See access.go
|
||||||
Name: "up",
|
accessCommand,
|
||||||
Usage: "uploads a file or directory to swarm using the HTTP API",
|
// See feeds.go
|
||||||
ArgsUsage: "<file>",
|
feedCommand,
|
||||||
Flags: []cli.Flag{SwarmEncryptedFlag},
|
// See list.go
|
||||||
Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash",
|
listCommand,
|
||||||
},
|
// See hash.go
|
||||||
{
|
hashCommand,
|
||||||
CustomHelpTemplate: helpTemplate,
|
// See download.go
|
||||||
Name: "access",
|
downloadCommand,
|
||||||
Usage: "encrypts a reference and embeds it into a root manifest",
|
// See manifest.go
|
||||||
ArgsUsage: "<ref>",
|
manifestCommand,
|
||||||
Description: "encrypts a reference and embeds it into a root manifest",
|
// See fs.go
|
||||||
Subcommands: []cli.Command{
|
fsCommand,
|
||||||
{
|
// See db.go
|
||||||
CustomHelpTemplate: helpTemplate,
|
dbCommand,
|
||||||
Name: "new",
|
|
||||||
Usage: "encrypts a reference and embeds it into a root manifest",
|
|
||||||
ArgsUsage: "<ref>",
|
|
||||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
|
||||||
Subcommands: []cli.Command{
|
|
||||||
{
|
|
||||||
Action: accessNewPass,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
utils.PasswordFileFlag,
|
|
||||||
SwarmDryRunFlag,
|
|
||||||
},
|
|
||||||
Name: "pass",
|
|
||||||
Usage: "encrypts a reference with a password and embeds it into a root manifest",
|
|
||||||
ArgsUsage: "<ref>",
|
|
||||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: accessNewPK,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
utils.PasswordFileFlag,
|
|
||||||
SwarmDryRunFlag,
|
|
||||||
SwarmAccessGrantKeyFlag,
|
|
||||||
},
|
|
||||||
Name: "pk",
|
|
||||||
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
|
|
||||||
ArgsUsage: "<ref>",
|
|
||||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: accessNewACT,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
SwarmAccessGrantKeysFlag,
|
|
||||||
SwarmDryRunFlag,
|
|
||||||
utils.PasswordFileFlag,
|
|
||||||
},
|
|
||||||
Name: "act",
|
|
||||||
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
|
|
||||||
ArgsUsage: "<ref>",
|
|
||||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "resource",
|
|
||||||
Usage: "(Advanced) Create and update Mutable Resources",
|
|
||||||
ArgsUsage: "<create|update|info>",
|
|
||||||
Description: "Works with Mutable Resource Updates",
|
|
||||||
Subcommands: []cli.Command{
|
|
||||||
{
|
|
||||||
Action: resourceCreate,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "create",
|
|
||||||
Usage: "creates a new Mutable Resource",
|
|
||||||
ArgsUsage: "<frequency>",
|
|
||||||
Description: "creates a new Mutable Resource",
|
|
||||||
Flags: []cli.Flag{SwarmResourceNameFlag, SwarmResourceDataOnCreateFlag, SwarmResourceMultihashFlag},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: resourceUpdate,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "update",
|
|
||||||
Usage: "updates the content of an existing Mutable Resource",
|
|
||||||
ArgsUsage: "<Manifest Address or ENS domain> <0x Hex data>",
|
|
||||||
Description: "updates the content of an existing Mutable Resource",
|
|
||||||
Flags: []cli.Flag{SwarmResourceMultihashFlag},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: resourceInfo,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "info",
|
|
||||||
Usage: "obtains information about an existing Mutable Resource",
|
|
||||||
ArgsUsage: "<Manifest Address or ENS domain>",
|
|
||||||
Description: "obtains information about an existing Mutable Resource",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: list,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "ls",
|
|
||||||
Usage: "list files and directories contained in a manifest",
|
|
||||||
ArgsUsage: "<manifest> [<prefix>]",
|
|
||||||
Description: "Lists files and directories contained in a manifest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: hash,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "hash",
|
|
||||||
Usage: "print the swarm hash of a file or directory",
|
|
||||||
ArgsUsage: "<file>",
|
|
||||||
Description: "Prints the swarm hash of file or directory",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: download,
|
|
||||||
Name: "down",
|
|
||||||
Flags: []cli.Flag{SwarmRecursiveFlag, SwarmAccessPasswordFlag},
|
|
||||||
Usage: "downloads a swarm manifest or a file inside a manifest",
|
|
||||||
ArgsUsage: " <uri> [<dir>]",
|
|
||||||
Description: `Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "manifest",
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Usage: "perform operations on swarm manifests",
|
|
||||||
ArgsUsage: "COMMAND",
|
|
||||||
Description: "Updates a MANIFEST by adding/removing/updating the hash of a path.\nCOMMAND could be: add, update, remove",
|
|
||||||
Subcommands: []cli.Command{
|
|
||||||
{
|
|
||||||
Action: manifestAdd,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "add",
|
|
||||||
Usage: "add a new path to the manifest",
|
|
||||||
ArgsUsage: "<MANIFEST> <path> <hash>",
|
|
||||||
Description: "Adds a new path to the manifest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: manifestUpdate,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "update",
|
|
||||||
Usage: "update the hash for an already existing path in the manifest",
|
|
||||||
ArgsUsage: "<MANIFEST> <path> <newhash>",
|
|
||||||
Description: "Update the hash for an already existing path in the manifest",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: manifestRemove,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "remove",
|
|
||||||
Usage: "removes a path from the manifest",
|
|
||||||
ArgsUsage: "<MANIFEST> <path>",
|
|
||||||
Description: "Removes a path from the manifest",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "fs",
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Usage: "perform FUSE operations",
|
|
||||||
ArgsUsage: "fs COMMAND",
|
|
||||||
Description: "Performs FUSE operations by mounting/unmounting/listing mount points. This assumes you already have a Swarm node running locally. For all operation you must reference the correct path to bzzd.ipc in order to communicate with the node",
|
|
||||||
Subcommands: []cli.Command{
|
|
||||||
{
|
|
||||||
Action: mount,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "mount",
|
|
||||||
Flags: []cli.Flag{utils.IPCPathFlag},
|
|
||||||
Usage: "mount a swarm hash to a mount point",
|
|
||||||
ArgsUsage: "swarm fs mount --ipcpath <path to bzzd.ipc> <manifest hash> <mount point>",
|
|
||||||
Description: "Mounts a Swarm manifest hash to a given mount point. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: unmount,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "unmount",
|
|
||||||
Flags: []cli.Flag{utils.IPCPathFlag},
|
|
||||||
Usage: "unmount a swarmfs mount",
|
|
||||||
ArgsUsage: "swarm fs unmount --ipcpath <path to bzzd.ipc> <mount point>",
|
|
||||||
Description: "Unmounts a swarmfs mount residing at <mount point>. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: listMounts,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "list",
|
|
||||||
Flags: []cli.Flag{utils.IPCPathFlag},
|
|
||||||
Usage: "list swarmfs mounts",
|
|
||||||
ArgsUsage: "swarm fs list --ipcpath <path to bzzd.ipc>",
|
|
||||||
Description: "Lists all mounted swarmfs volumes. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "db",
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Usage: "manage the local chunk database",
|
|
||||||
ArgsUsage: "db COMMAND",
|
|
||||||
Description: "Manage the local chunk database",
|
|
||||||
Subcommands: []cli.Command{
|
|
||||||
{
|
|
||||||
Action: dbExport,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "export",
|
|
||||||
Usage: "export a local chunk database as a tar archive (use - to send to stdout)",
|
|
||||||
ArgsUsage: "<chunkdb> <file>",
|
|
||||||
Description: `
|
|
||||||
Export a local chunk database as a tar archive (use - to send to stdout).
|
|
||||||
|
|
||||||
swarm db export ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
|
|
||||||
|
|
||||||
The export may be quite large, consider piping the output through the Unix
|
|
||||||
pv(1) tool to get a progress bar:
|
|
||||||
|
|
||||||
swarm db export ~/.ethereum/swarm/bzz-KEY/chunks - | pv > chunks.tar
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: dbImport,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "import",
|
|
||||||
Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
|
|
||||||
ArgsUsage: "<chunkdb> <file>",
|
|
||||||
Description: `Import chunks from a tar archive into a local chunk database (use - to read from stdin).
|
|
||||||
|
|
||||||
swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
|
|
||||||
|
|
||||||
The import may be quite large, consider piping the input through the Unix
|
|
||||||
pv(1) tool to get a progress bar:
|
|
||||||
|
|
||||||
pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks -`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Action: dbClean,
|
|
||||||
CustomHelpTemplate: helpTemplate,
|
|
||||||
Name: "clean",
|
|
||||||
Usage: "remove corrupt entries from a local chunk database",
|
|
||||||
ArgsUsage: "<chunkdb>",
|
|
||||||
Description: "Remove corrupt entries from a local chunk database",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// See config.go
|
// See config.go
|
||||||
DumpConfigCommand,
|
DumpConfigCommand,
|
||||||
}
|
}
|
||||||
|
@ -542,6 +172,7 @@ pv(1) tool to get a progress bar:
|
||||||
SwarmSwapAPIFlag,
|
SwarmSwapAPIFlag,
|
||||||
SwarmSyncDisabledFlag,
|
SwarmSyncDisabledFlag,
|
||||||
SwarmSyncUpdateDelay,
|
SwarmSyncUpdateDelay,
|
||||||
|
SwarmMaxStreamPeerServersFlag,
|
||||||
SwarmLightNodeEnabled,
|
SwarmLightNodeEnabled,
|
||||||
SwarmDeliverySkipCheckFlag,
|
SwarmDeliverySkipCheckFlag,
|
||||||
SwarmListenAddrFlag,
|
SwarmListenAddrFlag,
|
||||||
|
@ -697,7 +328,7 @@ func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.Pr
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPrivKey returns the private key of the specified bzzaccount
|
// getPrivKey returns the private key of the specified bzzaccount
|
||||||
// Used only by client commands, such as `resource`
|
// Used only by client commands, such as `feed`
|
||||||
func getPrivKey(ctx *cli.Context) *ecdsa.PrivateKey {
|
func getPrivKey(ctx *cli.Context) *ecdsa.PrivateKey {
|
||||||
// booting up the swarm node just as we do in bzzd action
|
// booting up the swarm node just as we do in bzzd action
|
||||||
bzzconfig, err := buildConfig(ctx)
|
bzzconfig, err := buildConfig(ctx)
|
||||||
|
@ -788,10 +419,10 @@ func setSwarmBootstrapNodes(ctx *cli.Context, cfg *node.Config) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.P2P.BootstrapNodes = []*discover.Node{}
|
cfg.P2P.BootstrapNodes = []*enode.Node{}
|
||||||
|
|
||||||
for _, url := range SwarmBootnodes {
|
for _, url := range SwarmBootnodes {
|
||||||
node, err := discover.ParseNode(url)
|
node, err := enode.ParseV4(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Bootstrap URL invalid", "enode", url, "err", err)
|
log.Error("Bootstrap URL invalid", "enode", url, "err", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,40 @@ import (
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var manifestCommand = cli.Command{
|
||||||
|
Name: "manifest",
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Usage: "perform operations on swarm manifests",
|
||||||
|
ArgsUsage: "COMMAND",
|
||||||
|
Description: "Updates a MANIFEST by adding/removing/updating the hash of a path.\nCOMMAND could be: add, update, remove",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Action: manifestAdd,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "add",
|
||||||
|
Usage: "add a new path to the manifest",
|
||||||
|
ArgsUsage: "<MANIFEST> <path> <hash>",
|
||||||
|
Description: "Adds a new path to the manifest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: manifestUpdate,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "update",
|
||||||
|
Usage: "update the hash for an already existing path in the manifest",
|
||||||
|
ArgsUsage: "<MANIFEST> <path> <newhash>",
|
||||||
|
Description: "Update the hash for an already existing path in the manifest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: manifestRemove,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "remove",
|
||||||
|
Usage: "removes a path from the manifest",
|
||||||
|
ArgsUsage: "<MANIFEST> <path>",
|
||||||
|
Description: "Removes a path from the manifest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// manifestAdd adds a new entry to the manifest at the given path.
|
// manifestAdd adds a new entry to the manifest at the given path.
|
||||||
// New entry hash, the last argument, must be the hash of a manifest
|
// New entry hash, the last argument, must be the hash of a manifest
|
||||||
// with only one entry, which meta-data will be added to the original manifest.
|
// with only one entry, which meta-data will be added to the original manifest.
|
||||||
|
|
|
@ -21,21 +21,31 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||||
|
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestManifestChange tests manifest add, update and remove
|
// TestManifestChange tests manifest add, update and remove
|
||||||
// cli commands without encryption.
|
// cli commands without encryption.
|
||||||
func TestManifestChange(t *testing.T) {
|
func TestManifestChange(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
testManifestChange(t, false)
|
testManifestChange(t, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestManifestChange tests manifest add, update and remove
|
// TestManifestChange tests manifest add, update and remove
|
||||||
// cli commands with encryption enabled.
|
// cli commands with encryption enabled.
|
||||||
func TestManifestChangeEncrypted(t *testing.T) {
|
func TestManifestChangeEncrypted(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
testManifestChange(t, true)
|
testManifestChange(t, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,8 +58,8 @@ func TestManifestChangeEncrypted(t *testing.T) {
|
||||||
// Argument encrypt controls whether to use encryption or not.
|
// Argument encrypt controls whether to use encryption or not.
|
||||||
func testManifestChange(t *testing.T, encrypt bool) {
|
func testManifestChange(t *testing.T, encrypt bool) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
cluster := newTestCluster(t, 1)
|
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||||
defer cluster.Shutdown()
|
defer srv.Close()
|
||||||
|
|
||||||
tmp, err := ioutil.TempDir("", "swarm-manifest-test")
|
tmp, err := ioutil.TempDir("", "swarm-manifest-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -85,7 +95,7 @@ func testManifestChange(t *testing.T, encrypt bool) {
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"--recursive",
|
"--recursive",
|
||||||
"--defaultpath",
|
"--defaultpath",
|
||||||
indexDataFilename,
|
indexDataFilename,
|
||||||
|
@ -100,7 +110,7 @@ func testManifestChange(t *testing.T, encrypt bool) {
|
||||||
|
|
||||||
checkHashLength(t, origManifestHash, encrypt)
|
checkHashLength(t, origManifestHash, encrypt)
|
||||||
|
|
||||||
client := swarm.NewClient(cluster.Nodes[0].URL)
|
client := swarm.NewClient(srv.URL)
|
||||||
|
|
||||||
// upload a new file and use its manifest to add it the original manifest.
|
// upload a new file and use its manifest to add it the original manifest.
|
||||||
t.Run("add", func(t *testing.T) {
|
t.Run("add", func(t *testing.T) {
|
||||||
|
@ -113,14 +123,14 @@ func testManifestChange(t *testing.T, encrypt bool) {
|
||||||
|
|
||||||
humansManifestHash := runSwarmExpectHash(t,
|
humansManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"up",
|
"up",
|
||||||
humansDataFilename,
|
humansDataFilename,
|
||||||
)
|
)
|
||||||
|
|
||||||
newManifestHash := runSwarmExpectHash(t,
|
newManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"manifest",
|
"manifest",
|
||||||
"add",
|
"add",
|
||||||
origManifestHash,
|
origManifestHash,
|
||||||
|
@ -168,14 +178,14 @@ func testManifestChange(t *testing.T, encrypt bool) {
|
||||||
|
|
||||||
robotsManifestHash := runSwarmExpectHash(t,
|
robotsManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"up",
|
"up",
|
||||||
robotsDataFilename,
|
robotsDataFilename,
|
||||||
)
|
)
|
||||||
|
|
||||||
newManifestHash := runSwarmExpectHash(t,
|
newManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"manifest",
|
"manifest",
|
||||||
"add",
|
"add",
|
||||||
origManifestHash,
|
origManifestHash,
|
||||||
|
@ -228,14 +238,14 @@ func testManifestChange(t *testing.T, encrypt bool) {
|
||||||
|
|
||||||
indexManifestHash := runSwarmExpectHash(t,
|
indexManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"up",
|
"up",
|
||||||
indexDataFilename,
|
indexDataFilename,
|
||||||
)
|
)
|
||||||
|
|
||||||
newManifestHash := runSwarmExpectHash(t,
|
newManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"manifest",
|
"manifest",
|
||||||
"update",
|
"update",
|
||||||
origManifestHash,
|
origManifestHash,
|
||||||
|
@ -286,14 +296,14 @@ func testManifestChange(t *testing.T, encrypt bool) {
|
||||||
|
|
||||||
humansManifestHash := runSwarmExpectHash(t,
|
humansManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"up",
|
"up",
|
||||||
robotsDataFilename,
|
robotsDataFilename,
|
||||||
)
|
)
|
||||||
|
|
||||||
newManifestHash := runSwarmExpectHash(t,
|
newManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"manifest",
|
"manifest",
|
||||||
"update",
|
"update",
|
||||||
origManifestHash,
|
origManifestHash,
|
||||||
|
@ -339,7 +349,7 @@ func testManifestChange(t *testing.T, encrypt bool) {
|
||||||
t.Run("remove", func(t *testing.T) {
|
t.Run("remove", func(t *testing.T) {
|
||||||
newManifestHash := runSwarmExpectHash(t,
|
newManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"manifest",
|
"manifest",
|
||||||
"remove",
|
"remove",
|
||||||
origManifestHash,
|
origManifestHash,
|
||||||
|
@ -367,7 +377,7 @@ func testManifestChange(t *testing.T, encrypt bool) {
|
||||||
t.Run("remove nested", func(t *testing.T) {
|
t.Run("remove nested", func(t *testing.T) {
|
||||||
newManifestHash := runSwarmExpectHash(t,
|
newManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"manifest",
|
"manifest",
|
||||||
"remove",
|
"remove",
|
||||||
origManifestHash,
|
origManifestHash,
|
||||||
|
@ -400,6 +410,10 @@ func testManifestChange(t *testing.T, encrypt bool) {
|
||||||
// TestNestedDefaultEntryUpdate tests if the default entry is updated
|
// TestNestedDefaultEntryUpdate tests if the default entry is updated
|
||||||
// if the file in nested manifest used for it is also updated.
|
// if the file in nested manifest used for it is also updated.
|
||||||
func TestNestedDefaultEntryUpdate(t *testing.T) {
|
func TestNestedDefaultEntryUpdate(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
testNestedDefaultEntryUpdate(t, false)
|
testNestedDefaultEntryUpdate(t, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,13 +421,17 @@ func TestNestedDefaultEntryUpdate(t *testing.T) {
|
||||||
// of encrypted upload is updated if the file in nested manifest
|
// of encrypted upload is updated if the file in nested manifest
|
||||||
// used for it is also updated.
|
// used for it is also updated.
|
||||||
func TestNestedDefaultEntryUpdateEncrypted(t *testing.T) {
|
func TestNestedDefaultEntryUpdateEncrypted(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
testNestedDefaultEntryUpdate(t, true)
|
testNestedDefaultEntryUpdate(t, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
|
func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
cluster := newTestCluster(t, 1)
|
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||||
defer cluster.Shutdown()
|
defer srv.Close()
|
||||||
|
|
||||||
tmp, err := ioutil.TempDir("", "swarm-manifest-test")
|
tmp, err := ioutil.TempDir("", "swarm-manifest-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -441,7 +459,7 @@ func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"--recursive",
|
"--recursive",
|
||||||
"--defaultpath",
|
"--defaultpath",
|
||||||
indexDataFilename,
|
indexDataFilename,
|
||||||
|
@ -456,7 +474,7 @@ func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
|
||||||
|
|
||||||
checkHashLength(t, origManifestHash, encrypt)
|
checkHashLength(t, origManifestHash, encrypt)
|
||||||
|
|
||||||
client := swarm.NewClient(cluster.Nodes[0].URL)
|
client := swarm.NewClient(srv.URL)
|
||||||
|
|
||||||
newIndexData := []byte("<h1>Ethereum Swarm</h1>")
|
newIndexData := []byte("<h1>Ethereum Swarm</h1>")
|
||||||
newIndexDataFilename := filepath.Join(tmp, "index.html")
|
newIndexDataFilename := filepath.Join(tmp, "index.html")
|
||||||
|
@ -467,14 +485,14 @@ func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) {
|
||||||
|
|
||||||
newIndexManifestHash := runSwarmExpectHash(t,
|
newIndexManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"up",
|
"up",
|
||||||
newIndexDataFilename,
|
newIndexDataFilename,
|
||||||
)
|
)
|
||||||
|
|
||||||
newManifestHash := runSwarmExpectHash(t,
|
newManifestHash := runSwarmExpectHash(t,
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"manifest",
|
"manifest",
|
||||||
"update",
|
"update",
|
||||||
origManifestHash,
|
origManifestHash,
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
package main
|
||||||
|
|
||||||
|
// Standard "mime" package rely on system-settings, see mime.osInitMime
|
||||||
|
// Swarm will run on many OS/Platform/Docker and must behave similar
|
||||||
|
// This command generates code to add common mime types based on mime.types file
|
||||||
|
//
|
||||||
|
// mime.types file provided by mailcap, which follow https://www.iana.org/assignments/media-types/media-types.xhtml
|
||||||
|
//
|
||||||
|
// Get last version of mime.types file by:
|
||||||
|
// docker run --rm -v $(pwd):/tmp alpine:edge /bin/sh -c "apk add -U mailcap; mv /etc/mime.types /tmp"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"flag"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
typesFlag = flag.String("types", "", "Input mime.types file")
|
||||||
|
packageFlag = flag.String("package", "", "Golang package in output file")
|
||||||
|
outFlag = flag.String("out", "", "Output file name for the generated mime types")
|
||||||
|
)
|
||||||
|
|
||||||
|
type mime struct {
|
||||||
|
Name string
|
||||||
|
Exts []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type templateParams struct {
|
||||||
|
PackageName string
|
||||||
|
Mimes []mime
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Parse and ensure all needed inputs are specified
|
||||||
|
flag.Parse()
|
||||||
|
if *typesFlag == "" {
|
||||||
|
log.Fatalf("--types is required")
|
||||||
|
}
|
||||||
|
if *packageFlag == "" {
|
||||||
|
log.Fatalf("--types is required")
|
||||||
|
}
|
||||||
|
if *outFlag == "" {
|
||||||
|
log.Fatalf("--out is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
params := templateParams{
|
||||||
|
PackageName: *packageFlag,
|
||||||
|
}
|
||||||
|
|
||||||
|
types, err := ioutil.ReadFile(*typesFlag)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(bytes.NewReader(types))
|
||||||
|
for scanner.Scan() {
|
||||||
|
txt := scanner.Text()
|
||||||
|
if strings.HasPrefix(txt, "#") || len(txt) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.Fields(txt)
|
||||||
|
if len(parts) == 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
params.Mimes = append(params.Mimes, mime{parts[0], parts[1:]})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = scanner.Err(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := bytes.NewBuffer([]byte{})
|
||||||
|
|
||||||
|
if err := template.Must(template.New("_").Parse(tpl)).Execute(result, params); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(*outFlag, result.Bytes(), 0600); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tpl = `// Code generated by github.com/ethereum/go-ethereum/cmd/swarm/mimegen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package {{ .PackageName }}
|
||||||
|
|
||||||
|
import "mime"
|
||||||
|
func init() {
|
||||||
|
var mimeTypes = map[string]string{
|
||||||
|
{{- range .Mimes -}}
|
||||||
|
{{ $name := .Name -}}
|
||||||
|
{{- range .Exts }}
|
||||||
|
".{{ . }}": "{{ $name | html }}",
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
}
|
||||||
|
for ext, name := range mimeTypes {
|
||||||
|
if err := mime.AddExtensionType(ext, name); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
169
cmd/swarm/mru.go
|
@ -1,169 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of go-ethereum.
|
|
||||||
//
|
|
||||||
// go-ethereum is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// go-ethereum is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Command resource allows the user to create and update signed mutable resource updates
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
|
||||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/mru"
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewGenericSigner(ctx *cli.Context) mru.Signer {
|
|
||||||
return mru.NewGenericSigner(getPrivKey(ctx))
|
|
||||||
}
|
|
||||||
|
|
||||||
// swarm resource create <frequency> [--name <name>] [--data <0x Hexdata> [--multihash=false]]
|
|
||||||
// swarm resource update <Manifest Address or ENS domain> <0x Hexdata> [--multihash=false]
|
|
||||||
// swarm resource info <Manifest Address or ENS domain>
|
|
||||||
|
|
||||||
func resourceCreate(ctx *cli.Context) {
|
|
||||||
args := ctx.Args()
|
|
||||||
|
|
||||||
var (
|
|
||||||
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
|
||||||
client = swarm.NewClient(bzzapi)
|
|
||||||
multihash = ctx.Bool(SwarmResourceMultihashFlag.Name)
|
|
||||||
initialData = ctx.String(SwarmResourceDataOnCreateFlag.Name)
|
|
||||||
name = ctx.String(SwarmResourceNameFlag.Name)
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(args) < 1 {
|
|
||||||
fmt.Println("Incorrect number of arguments")
|
|
||||||
cli.ShowCommandHelpAndExit(ctx, "create", 1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
signer := NewGenericSigner(ctx)
|
|
||||||
frequency, err := strconv.ParseUint(args[0], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Frequency formatting error: %s\n", err.Error())
|
|
||||||
cli.ShowCommandHelpAndExit(ctx, "create", 1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata := mru.ResourceMetadata{
|
|
||||||
Name: name,
|
|
||||||
Frequency: frequency,
|
|
||||||
Owner: signer.Address(),
|
|
||||||
}
|
|
||||||
|
|
||||||
var newResourceRequest *mru.Request
|
|
||||||
if initialData != "" {
|
|
||||||
initialDataBytes, err := hexutil.Decode(initialData)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error parsing data: %s\n", err.Error())
|
|
||||||
cli.ShowCommandHelpAndExit(ctx, "create", 1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
newResourceRequest, err = mru.NewCreateUpdateRequest(&metadata)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Error creating new resource request: %s", err)
|
|
||||||
}
|
|
||||||
newResourceRequest.SetData(initialDataBytes, multihash)
|
|
||||||
if err = newResourceRequest.Sign(signer); err != nil {
|
|
||||||
utils.Fatalf("Error signing resource update: %s", err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newResourceRequest, err = mru.NewCreateRequest(&metadata)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Error creating new resource request: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
manifestAddress, err := client.CreateResource(newResourceRequest)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Error creating resource: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Println(manifestAddress) // output manifest address to the user in a single line (useful for other commands to pick up)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func resourceUpdate(ctx *cli.Context) {
|
|
||||||
args := ctx.Args()
|
|
||||||
|
|
||||||
var (
|
|
||||||
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
|
||||||
client = swarm.NewClient(bzzapi)
|
|
||||||
multihash = ctx.Bool(SwarmResourceMultihashFlag.Name)
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(args) < 2 {
|
|
||||||
fmt.Println("Incorrect number of arguments")
|
|
||||||
cli.ShowCommandHelpAndExit(ctx, "update", 1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
signer := NewGenericSigner(ctx)
|
|
||||||
manifestAddressOrDomain := args[0]
|
|
||||||
data, err := hexutil.Decode(args[1])
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Error parsing data: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve resource status and metadata out of the manifest
|
|
||||||
updateRequest, err := client.GetResourceMetadata(manifestAddressOrDomain)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Error retrieving resource status: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the new data
|
|
||||||
updateRequest.SetData(data, multihash)
|
|
||||||
|
|
||||||
// sign update
|
|
||||||
if err = updateRequest.Sign(signer); err != nil {
|
|
||||||
utils.Fatalf("Error signing resource update: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// post update
|
|
||||||
err = client.UpdateResource(updateRequest)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Error updating resource: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func resourceInfo(ctx *cli.Context) {
|
|
||||||
var (
|
|
||||||
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
|
||||||
client = swarm.NewClient(bzzapi)
|
|
||||||
)
|
|
||||||
args := ctx.Args()
|
|
||||||
if len(args) < 1 {
|
|
||||||
fmt.Println("Incorrect number of arguments.")
|
|
||||||
cli.ShowCommandHelpAndExit(ctx, "info", 1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
manifestAddressOrDomain := args[0]
|
|
||||||
metadata, err := client.GetResourceMetadata(manifestAddressOrDomain)
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Error retrieving resource metadata: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
encodedMetadata, err := metadata.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
utils.Fatalf("Error encoding metadata to JSON for display:%s", err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(encodedMetadata))
|
|
||||||
}
|
|
|
@ -19,6 +19,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
|
@ -39,8 +40,12 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/ethereum/go-ethereum/swarm"
|
"github.com/ethereum/go-ethereum/swarm"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var loglevel = flag.Int("loglevel", 3, "verbosity of logs")
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Run the app if we've been exec'd as "swarm-test" in runSwarm.
|
// Run the app if we've been exec'd as "swarm-test" in runSwarm.
|
||||||
reexec.Register("swarm-test", func() {
|
reexec.Register("swarm-test", func() {
|
||||||
|
@ -52,6 +57,9 @@ func init() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func serverFunc(api *api.API) swarmhttp.TestServer {
|
||||||
|
return swarmhttp.NewServer(api, "")
|
||||||
|
}
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
// check if we have been reexec'd
|
// check if we have been reexec'd
|
||||||
if reexec.Init() {
|
if reexec.Init() {
|
||||||
|
@ -234,6 +242,7 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
|
||||||
// start the node
|
// start the node
|
||||||
node.Cmd = runSwarm(t,
|
node.Cmd = runSwarm(t,
|
||||||
"--port", p2pPort,
|
"--port", p2pPort,
|
||||||
|
"--nat", "extip:127.0.0.1",
|
||||||
"--nodiscover",
|
"--nodiscover",
|
||||||
"--datadir", dir,
|
"--datadir", dir,
|
||||||
"--ipcpath", conf.IPCPath,
|
"--ipcpath", conf.IPCPath,
|
||||||
|
@ -241,7 +250,7 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
|
||||||
"--bzzaccount", bzzaccount,
|
"--bzzaccount", bzzaccount,
|
||||||
"--bzznetworkid", "321",
|
"--bzznetworkid", "321",
|
||||||
"--bzzport", httpPort,
|
"--bzzport", httpPort,
|
||||||
"--verbosity", "6",
|
"--verbosity", fmt.Sprint(*loglevel),
|
||||||
)
|
)
|
||||||
node.Cmd.InputLine(testPassphrase)
|
node.Cmd.InputLine(testPassphrase)
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -284,8 +293,8 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
|
||||||
if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
|
if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
node.Enode = fmt.Sprintf("enode://%s@127.0.0.1:%s", nodeInfo.ID, p2pPort)
|
node.Enode = nodeInfo.Enode
|
||||||
|
node.IpcPath = conf.IPCPath
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,6 +318,7 @@ func newTestNode(t *testing.T, dir string) *testNode {
|
||||||
// start the node
|
// start the node
|
||||||
node.Cmd = runSwarm(t,
|
node.Cmd = runSwarm(t,
|
||||||
"--port", p2pPort,
|
"--port", p2pPort,
|
||||||
|
"--nat", "extip:127.0.0.1",
|
||||||
"--nodiscover",
|
"--nodiscover",
|
||||||
"--datadir", dir,
|
"--datadir", dir,
|
||||||
"--ipcpath", conf.IPCPath,
|
"--ipcpath", conf.IPCPath,
|
||||||
|
@ -316,7 +326,7 @@ func newTestNode(t *testing.T, dir string) *testNode {
|
||||||
"--bzzaccount", account.Address.String(),
|
"--bzzaccount", account.Address.String(),
|
||||||
"--bzznetworkid", "321",
|
"--bzznetworkid", "321",
|
||||||
"--bzzport", httpPort,
|
"--bzzport", httpPort,
|
||||||
"--verbosity", "6",
|
"--verbosity", fmt.Sprint(*loglevel),
|
||||||
)
|
)
|
||||||
node.Cmd.InputLine(testPassphrase)
|
node.Cmd.InputLine(testPassphrase)
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -359,9 +369,8 @@ func newTestNode(t *testing.T, dir string) *testNode {
|
||||||
if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
|
if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
node.Enode = fmt.Sprintf("enode://%s@127.0.0.1:%s", nodeInfo.ID, p2pPort)
|
node.Enode = nodeInfo.Enode
|
||||||
node.IpcPath = conf.IPCPath
|
node.IpcPath = conf.IPCPath
|
||||||
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,320 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/multihash"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/storage/feed"
|
||||||
|
colorable "github.com/mattn/go-colorable"
|
||||||
|
"github.com/pborman/uuid"
|
||||||
|
cli "gopkg.in/urfave/cli.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
feedRandomDataLength = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: retrieve with manifest + extract repeating code
|
||||||
|
func cliFeedUploadAndSync(c *cli.Context) error {
|
||||||
|
|
||||||
|
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(verbosity), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))))
|
||||||
|
|
||||||
|
defer func(now time.Time) { log.Info("total time", "time", time.Since(now), "size (kb)", filesize) }(time.Now())
|
||||||
|
|
||||||
|
generateEndpoints(scheme, cluster, from, to)
|
||||||
|
|
||||||
|
log.Info("generating and uploading MRUs to " + endpoints[0] + " and syncing")
|
||||||
|
|
||||||
|
// create a random private key to sign updates with and derive the address
|
||||||
|
pkFile, err := ioutil.TempFile("", "swarm-feed-smoke-test")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer pkFile.Close()
|
||||||
|
defer os.Remove(pkFile.Name())
|
||||||
|
|
||||||
|
privkeyHex := "0000000000000000000000000000000000000000000000000000000000001976"
|
||||||
|
privKey, err := crypto.HexToECDSA(privkeyHex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user := crypto.PubkeyToAddress(privKey.PublicKey)
|
||||||
|
userHex := hexutil.Encode(user.Bytes())
|
||||||
|
|
||||||
|
// save the private key to a file
|
||||||
|
_, err = io.WriteString(pkFile, privkeyHex)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep hex strings for topic and subtopic
|
||||||
|
var topicHex string
|
||||||
|
var subTopicHex string
|
||||||
|
|
||||||
|
// and create combination hex topics for bzz-feed retrieval
|
||||||
|
// xor'ed with topic (zero-value topic if no topic)
|
||||||
|
var subTopicOnlyHex string
|
||||||
|
var mergedSubTopicHex string
|
||||||
|
|
||||||
|
// generate random topic and subtopic and put a hex on them
|
||||||
|
topicBytes, err := generateRandomData(feed.TopicLength)
|
||||||
|
topicHex = hexutil.Encode(topicBytes)
|
||||||
|
subTopicBytes, err := generateRandomData(8)
|
||||||
|
subTopicHex = hexutil.Encode(subTopicBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mergedSubTopic, err := feed.NewTopic(subTopicHex, topicBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
mergedSubTopicHex = hexutil.Encode(mergedSubTopic[:])
|
||||||
|
subTopicOnlyBytes, err := feed.NewTopic(subTopicHex, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
subTopicOnlyHex = hexutil.Encode(subTopicOnlyBytes[:])
|
||||||
|
|
||||||
|
// create feed manifest, topic only
|
||||||
|
var out bytes.Buffer
|
||||||
|
cmd := exec.Command("swarm", "--bzzapi", endpoints[0], "feed", "create", "--topic", topicHex, "--user", userHex)
|
||||||
|
cmd.Stdout = &out
|
||||||
|
log.Debug("create feed manifest topic cmd", "cmd", cmd)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
manifestWithTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
|
||||||
|
if len(manifestWithTopic) != 64 {
|
||||||
|
return fmt.Errorf("unknown feed create manifest hash format (topic): (%d) %s", len(out.String()), manifestWithTopic)
|
||||||
|
}
|
||||||
|
log.Debug("create topic feed", "manifest", manifestWithTopic)
|
||||||
|
out.Reset()
|
||||||
|
|
||||||
|
// create feed manifest, subtopic only
|
||||||
|
cmd = exec.Command("swarm", "--bzzapi", endpoints[0], "feed", "create", "--name", subTopicHex, "--user", userHex)
|
||||||
|
cmd.Stdout = &out
|
||||||
|
log.Debug("create feed manifest subtopic cmd", "cmd", cmd)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
manifestWithSubTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
|
||||||
|
if len(manifestWithSubTopic) != 64 {
|
||||||
|
return fmt.Errorf("unknown feed create manifest hash format (subtopic): (%d) %s", len(out.String()), manifestWithSubTopic)
|
||||||
|
}
|
||||||
|
log.Debug("create subtopic feed", "manifest", manifestWithTopic)
|
||||||
|
out.Reset()
|
||||||
|
|
||||||
|
// create feed manifest, merged topic
|
||||||
|
cmd = exec.Command("swarm", "--bzzapi", endpoints[0], "feed", "create", "--topic", topicHex, "--name", subTopicHex, "--user", userHex)
|
||||||
|
cmd.Stdout = &out
|
||||||
|
log.Debug("create feed manifest mergetopic cmd", "cmd", cmd)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
manifestWithMergedTopic := strings.TrimRight(out.String(), string([]byte{0x0a}))
|
||||||
|
if len(manifestWithMergedTopic) != 64 {
|
||||||
|
return fmt.Errorf("unknown feed create manifest hash format (mergedtopic): (%d) %s", len(out.String()), manifestWithMergedTopic)
|
||||||
|
}
|
||||||
|
log.Debug("create mergedtopic feed", "manifest", manifestWithMergedTopic)
|
||||||
|
out.Reset()
|
||||||
|
|
||||||
|
// create test data
|
||||||
|
data, err := generateRandomData(feedRandomDataLength)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h := md5.New()
|
||||||
|
h.Write(data)
|
||||||
|
dataHash := h.Sum(nil)
|
||||||
|
dataHex := hexutil.Encode(data)
|
||||||
|
|
||||||
|
// update with topic
|
||||||
|
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--topic", topicHex, dataHex)
|
||||||
|
cmd.Stdout = &out
|
||||||
|
log.Debug("update feed manifest topic cmd", "cmd", cmd)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debug("feed update topic", "out", out)
|
||||||
|
out.Reset()
|
||||||
|
|
||||||
|
// update with subtopic
|
||||||
|
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--name", subTopicHex, dataHex)
|
||||||
|
cmd.Stdout = &out
|
||||||
|
log.Debug("update feed manifest subtopic cmd", "cmd", cmd)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debug("feed update subtopic", "out", out)
|
||||||
|
out.Reset()
|
||||||
|
|
||||||
|
// update with merged topic
|
||||||
|
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--topic", topicHex, "--name", subTopicHex, dataHex)
|
||||||
|
cmd.Stdout = &out
|
||||||
|
log.Debug("update feed manifest merged topic cmd", "cmd", cmd)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debug("feed update mergedtopic", "out", out)
|
||||||
|
out.Reset()
|
||||||
|
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
// retrieve the data
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
// raw retrieve, topic only
|
||||||
|
for _, hex := range []string{topicHex, subTopicOnlyHex, mergedSubTopicHex} {
|
||||||
|
wg.Add(1)
|
||||||
|
ruid := uuid.New()[:8]
|
||||||
|
go func(hex string, endpoint string, ruid string) {
|
||||||
|
for {
|
||||||
|
err := fetchFeed(hex, userHex, endpoint, dataHash, ruid)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(hex, endpoint, ruid)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
log.Info("all endpoints synced random data successfully")
|
||||||
|
|
||||||
|
// upload test file
|
||||||
|
log.Info("uploading to " + endpoints[0] + " and syncing")
|
||||||
|
|
||||||
|
f, cleanup := generateRandomFile(filesize * 1000)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
hash, err := upload(f, endpoints[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hashBytes, err := hexutil.Decode("0x" + hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
multihashHex := hexutil.Encode(multihash.ToMultihash(hashBytes))
|
||||||
|
|
||||||
|
fileHash, err := digest(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fileHash))
|
||||||
|
|
||||||
|
// update file with topic
|
||||||
|
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--topic", topicHex, multihashHex)
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debug("feed update topic", "out", out)
|
||||||
|
out.Reset()
|
||||||
|
|
||||||
|
// update file with subtopic
|
||||||
|
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--name", subTopicHex, multihashHex)
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debug("feed update subtopic", "out", out)
|
||||||
|
out.Reset()
|
||||||
|
|
||||||
|
// update file with merged topic
|
||||||
|
cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", endpoints[0], "feed", "update", "--topic", topicHex, "--name", subTopicHex, multihashHex)
|
||||||
|
cmd.Stdout = &out
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debug("feed update mergedtopic", "out", out)
|
||||||
|
out.Reset()
|
||||||
|
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
for _, endpoint := range endpoints {
|
||||||
|
|
||||||
|
// manifest retrieve, topic only
|
||||||
|
for _, url := range []string{manifestWithTopic, manifestWithSubTopic, manifestWithMergedTopic} {
|
||||||
|
wg.Add(1)
|
||||||
|
ruid := uuid.New()[:8]
|
||||||
|
go func(url string, endpoint string, ruid string) {
|
||||||
|
for {
|
||||||
|
err := fetch(url, endpoint, fileHash, ruid)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(url, endpoint, ruid)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
log.Info("all endpoints synced random file successfully")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchFeed(topic string, user string, endpoint string, original []byte, ruid string) error {
|
||||||
|
log.Trace("sleeping", "ruid", ruid)
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
|
||||||
|
log.Trace("http get request (feed)", "ruid", ruid, "api", endpoint, "topic", topic, "user", user)
|
||||||
|
res, err := http.Get(endpoint + "/bzz-feed:/?topic=" + topic + "&user=" + user)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Trace("http get response (feed)", "ruid", ruid, "api", endpoint, "topic", topic, "user", user, "code", res.StatusCode, "len", res.ContentLength)
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return fmt.Errorf("expected status code %d, got %v (ruid %v)", 200, res.StatusCode, ruid)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
rdigest, err := digest(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err.Error(), "ruid", ruid)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(rdigest, original) {
|
||||||
|
err := fmt.Errorf("downloaded imported file md5=%x is not the same as the generated one=%x", rdigest, original)
|
||||||
|
log.Warn(err.Error(), "ruid", ruid)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Trace("downloaded file matches random file", "ruid", ruid, "len", res.ContentLength)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -21,7 +21,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
colorable "github.com/mattn/go-colorable"
|
|
||||||
|
|
||||||
cli "gopkg.in/urfave/cli.v1"
|
cli "gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
@ -34,11 +33,10 @@ var (
|
||||||
filesize int
|
filesize int
|
||||||
from int
|
from int
|
||||||
to int
|
to int
|
||||||
|
verbosity int
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.PrintOrigins(true)
|
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
|
|
||||||
|
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "smoke-test"
|
app.Name = "smoke-test"
|
||||||
|
@ -80,6 +78,12 @@ func main() {
|
||||||
Usage: "file size for generated random file in KB",
|
Usage: "file size for generated random file in KB",
|
||||||
Destination: &filesize,
|
Destination: &filesize,
|
||||||
},
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "verbosity",
|
||||||
|
Value: 1,
|
||||||
|
Usage: "verbosity",
|
||||||
|
Destination: &verbosity,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
|
@ -89,6 +93,12 @@ func main() {
|
||||||
Usage: "upload and sync",
|
Usage: "upload and sync",
|
||||||
Action: cliUploadAndSync,
|
Action: cliUploadAndSync,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "feed_sync",
|
||||||
|
Aliases: []string{"f"},
|
||||||
|
Usage: "feed update generate, upload and sync",
|
||||||
|
Action: cliFeedUploadAndSync,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Sort(cli.FlagsByName(app.Flags))
|
sort.Sort(cli.FlagsByName(app.Flags))
|
||||||
|
|
|
@ -19,7 +19,8 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"crypto/rand"
|
crand "crypto/rand"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -85,7 +86,6 @@ func cliUploadAndSync(c *cli.Context) error {
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
for _, endpoint := range endpoints {
|
for _, endpoint := range endpoints {
|
||||||
endpoint := endpoint
|
|
||||||
ruid := uuid.New()[:8]
|
ruid := uuid.New()[:8]
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(endpoint string, ruid string) {
|
go func(endpoint string, ruid string) {
|
||||||
|
@ -166,6 +166,18 @@ func digest(r io.Reader) ([]byte, error) {
|
||||||
return h.Sum(nil), nil
|
return h.Sum(nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generates random data in heap buffer
|
||||||
|
func generateRandomData(datasize int) ([]byte, error) {
|
||||||
|
b := make([]byte, datasize)
|
||||||
|
c, err := crand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if c != datasize {
|
||||||
|
return nil, errors.New("short read")
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
// generateRandomFile is creating a temporary file with the requested byte size
|
// generateRandomFile is creating a temporary file with the requested byte size
|
||||||
func generateRandomFile(size int) (f *os.File, teardown func()) {
|
func generateRandomFile(size int) (f *os.File, teardown func()) {
|
||||||
// create a tmp file
|
// create a tmp file
|
||||||
|
@ -181,7 +193,7 @@ func generateRandomFile(size int) (f *os.File, teardown func()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, size)
|
buf := make([]byte, size)
|
||||||
_, err = rand.Read(buf)
|
_, err = crand.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,21 +22,31 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func upload(ctx *cli.Context) {
|
var upCommand = cli.Command{
|
||||||
|
Action: upload,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "up",
|
||||||
|
Usage: "uploads a file or directory to swarm using the HTTP API",
|
||||||
|
ArgsUsage: "<file>",
|
||||||
|
Flags: []cli.Flag{SwarmEncryptedFlag},
|
||||||
|
Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash",
|
||||||
|
}
|
||||||
|
|
||||||
|
func upload(ctx *cli.Context) {
|
||||||
args := ctx.Args()
|
args := ctx.Args()
|
||||||
var (
|
var (
|
||||||
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||||
|
@ -47,9 +57,16 @@ func upload(ctx *cli.Context) {
|
||||||
mimeType = ctx.GlobalString(SwarmUploadMimeType.Name)
|
mimeType = ctx.GlobalString(SwarmUploadMimeType.Name)
|
||||||
client = swarm.NewClient(bzzapi)
|
client = swarm.NewClient(bzzapi)
|
||||||
toEncrypt = ctx.Bool(SwarmEncryptedFlag.Name)
|
toEncrypt = ctx.Bool(SwarmEncryptedFlag.Name)
|
||||||
|
autoDefaultPath = false
|
||||||
file string
|
file string
|
||||||
)
|
)
|
||||||
|
if autoDefaultPathString := os.Getenv(SWARM_AUTO_DEFAULTPATH); autoDefaultPathString != "" {
|
||||||
|
b, err := strconv.ParseBool(autoDefaultPathString)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("invalid environment variable %s: %v", SWARM_AUTO_DEFAULTPATH, err)
|
||||||
|
}
|
||||||
|
autoDefaultPath = b
|
||||||
|
}
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
if fromStdin {
|
if fromStdin {
|
||||||
tmp, err := ioutil.TempFile("", "swarm-stdin")
|
tmp, err := ioutil.TempFile("", "swarm-stdin")
|
||||||
|
@ -98,6 +115,15 @@ func upload(ctx *cli.Context) {
|
||||||
if !recursive {
|
if !recursive {
|
||||||
return "", errors.New("Argument is a directory and recursive upload is disabled")
|
return "", errors.New("Argument is a directory and recursive upload is disabled")
|
||||||
}
|
}
|
||||||
|
if autoDefaultPath && defaultPath == "" {
|
||||||
|
defaultEntryCandidate := path.Join(file, "index.html")
|
||||||
|
log.Debug("trying to find default path", "path", defaultEntryCandidate)
|
||||||
|
defaultEntryStat, err := os.Stat(defaultEntryCandidate)
|
||||||
|
if err == nil && !defaultEntryStat.IsDir() {
|
||||||
|
log.Debug("setting auto detected default path", "path", defaultEntryCandidate)
|
||||||
|
defaultPath = defaultEntryCandidate
|
||||||
|
}
|
||||||
|
}
|
||||||
if defaultPath != "" {
|
if defaultPath != "" {
|
||||||
// construct absolute default path
|
// construct absolute default path
|
||||||
absDefaultPath, _ := filepath.Abs(defaultPath)
|
absDefaultPath, _ := filepath.Abs(defaultPath)
|
||||||
|
@ -118,10 +144,9 @@ func upload(ctx *cli.Context) {
|
||||||
return "", fmt.Errorf("error opening file: %s", err)
|
return "", fmt.Errorf("error opening file: %s", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
if mimeType == "" {
|
if mimeType != "" {
|
||||||
mimeType = detectMimeType(file)
|
|
||||||
}
|
|
||||||
f.ContentType = mimeType
|
f.ContentType = mimeType
|
||||||
|
}
|
||||||
return client.Upload(f, "", toEncrypt)
|
return client.Upload(f, "", toEncrypt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,6 +163,12 @@ func upload(ctx *cli.Context) {
|
||||||
// 3. cleans the path, e.g. /a/b/../c -> /a/c
|
// 3. cleans the path, e.g. /a/b/../c -> /a/c
|
||||||
// Note, it has limitations, e.g. ~someuser/tmp will not be expanded
|
// Note, it has limitations, e.g. ~someuser/tmp will not be expanded
|
||||||
func expandPath(p string) string {
|
func expandPath(p string) string {
|
||||||
|
if i := strings.Index(p, ":"); i > 0 {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
if i := strings.Index(p, "@"); i > 0 {
|
||||||
|
return p
|
||||||
|
}
|
||||||
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
|
if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
|
||||||
if home := homeDir(); home != "" {
|
if home := homeDir(); home != "" {
|
||||||
p = home + p[1:]
|
p = home + p[1:]
|
||||||
|
@ -155,19 +186,3 @@ func homeDir() string {
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectMimeType(file string) string {
|
|
||||||
if ext := filepath.Ext(file); ext != "" {
|
|
||||||
return mime.TypeByExtension(ext)
|
|
||||||
}
|
|
||||||
f, err := os.Open(file)
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
buf := make([]byte, 512)
|
|
||||||
if n, _ := f.Read(buf); n > 0 {
|
|
||||||
return http.DetectContentType(buf)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -26,17 +25,18 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||||
colorable "github.com/mattn/go-colorable"
|
swarmhttp "github.com/ethereum/go-ethereum/swarm/api/http"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/testutil"
|
||||||
|
"github.com/mattn/go-colorable"
|
||||||
)
|
)
|
||||||
|
|
||||||
var loglevel = flag.Int("loglevel", 3, "verbosity of logs")
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
log.PrintOrigins(true)
|
log.PrintOrigins(true)
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
|
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))))
|
||||||
|
@ -45,18 +45,31 @@ func init() {
|
||||||
// TestCLISwarmUp tests that running 'swarm up' makes the resulting file
|
// TestCLISwarmUp tests that running 'swarm up' makes the resulting file
|
||||||
// available from all nodes via the HTTP API
|
// available from all nodes via the HTTP API
|
||||||
func TestCLISwarmUp(t *testing.T) {
|
func TestCLISwarmUp(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
testCLISwarmUp(false, t)
|
testCLISwarmUp(false, t)
|
||||||
}
|
}
|
||||||
func TestCLISwarmUpRecursive(t *testing.T) {
|
func TestCLISwarmUpRecursive(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
testCLISwarmUpRecursive(false, t)
|
testCLISwarmUpRecursive(false, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestCLISwarmUpEncrypted tests that running 'swarm encrypted-up' makes the resulting file
|
// TestCLISwarmUpEncrypted tests that running 'swarm encrypted-up' makes the resulting file
|
||||||
// available from all nodes via the HTTP API
|
// available from all nodes via the HTTP API
|
||||||
func TestCLISwarmUpEncrypted(t *testing.T) {
|
func TestCLISwarmUpEncrypted(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
testCLISwarmUp(true, t)
|
testCLISwarmUp(true, t)
|
||||||
}
|
}
|
||||||
func TestCLISwarmUpEncryptedRecursive(t *testing.T) {
|
func TestCLISwarmUpEncryptedRecursive(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
testCLISwarmUpRecursive(true, t)
|
testCLISwarmUpRecursive(true, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,33 +78,22 @@ func testCLISwarmUp(toEncrypt bool, t *testing.T) {
|
||||||
cluster := newTestCluster(t, 3)
|
cluster := newTestCluster(t, 3)
|
||||||
defer cluster.Shutdown()
|
defer cluster.Shutdown()
|
||||||
|
|
||||||
// create a tmp file
|
tmpFileName := testutil.TempFileWithContent(t, data)
|
||||||
tmp, err := ioutil.TempFile("", "swarm-test")
|
defer os.Remove(tmpFileName)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer tmp.Close()
|
|
||||||
defer os.Remove(tmp.Name())
|
|
||||||
|
|
||||||
// write data to file
|
// write data to file
|
||||||
data := "notsorandomdata"
|
|
||||||
_, err = io.WriteString(tmp, data)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hashRegexp := `[a-f\d]{64}`
|
hashRegexp := `[a-f\d]{64}`
|
||||||
flags := []string{
|
flags := []string{
|
||||||
"--bzzapi", cluster.Nodes[0].URL,
|
"--bzzapi", cluster.Nodes[0].URL,
|
||||||
"up",
|
"up",
|
||||||
tmp.Name()}
|
tmpFileName}
|
||||||
if toEncrypt {
|
if toEncrypt {
|
||||||
hashRegexp = `[a-f\d]{128}`
|
hashRegexp = `[a-f\d]{128}`
|
||||||
flags = []string{
|
flags = []string{
|
||||||
"--bzzapi", cluster.Nodes[0].URL,
|
"--bzzapi", cluster.Nodes[0].URL,
|
||||||
"up",
|
"up",
|
||||||
"--encrypt",
|
"--encrypt",
|
||||||
tmp.Name()}
|
tmpFileName}
|
||||||
}
|
}
|
||||||
// upload the file with 'swarm up' and expect a hash
|
// upload the file with 'swarm up' and expect a hash
|
||||||
log.Info(fmt.Sprintf("uploading file with 'swarm up'"))
|
log.Info(fmt.Sprintf("uploading file with 'swarm up'"))
|
||||||
|
@ -191,7 +193,6 @@ func testCLISwarmUpRecursive(toEncrypt bool, t *testing.T) {
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpUploadDir)
|
defer os.RemoveAll(tmpUploadDir)
|
||||||
// create tmp files
|
// create tmp files
|
||||||
data := "notsorandomdata"
|
|
||||||
for _, path := range []string{"tmp1", "tmp2"} {
|
for _, path := range []string{"tmp1", "tmp2"} {
|
||||||
if err := ioutil.WriteFile(filepath.Join(tmpUploadDir, path), bytes.NewBufferString(data).Bytes(), 0644); err != nil {
|
if err := ioutil.WriteFile(filepath.Join(tmpUploadDir, path), bytes.NewBufferString(data).Bytes(), 0644); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -231,8 +232,7 @@ func testCLISwarmUpRecursive(toEncrypt bool, t *testing.T) {
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmpDownload)
|
defer os.RemoveAll(tmpDownload)
|
||||||
bzzLocator := "bzz:/" + hash
|
bzzLocator := "bzz:/" + hash
|
||||||
flagss := []string{}
|
flagss := []string{
|
||||||
flagss = []string{
|
|
||||||
"--bzzapi", cluster.Nodes[0].URL,
|
"--bzzapi", cluster.Nodes[0].URL,
|
||||||
"down",
|
"down",
|
||||||
"--recursive",
|
"--recursive",
|
||||||
|
@ -277,6 +277,9 @@ func testCLISwarmUpRecursive(toEncrypt bool, t *testing.T) {
|
||||||
// TestCLISwarmUpDefaultPath tests swarm recursive upload with relative and absolute
|
// TestCLISwarmUpDefaultPath tests swarm recursive upload with relative and absolute
|
||||||
// default paths and with encryption.
|
// default paths and with encryption.
|
||||||
func TestCLISwarmUpDefaultPath(t *testing.T) {
|
func TestCLISwarmUpDefaultPath(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
testCLISwarmUpDefaultPath(false, false, t)
|
testCLISwarmUpDefaultPath(false, false, t)
|
||||||
testCLISwarmUpDefaultPath(false, true, t)
|
testCLISwarmUpDefaultPath(false, true, t)
|
||||||
testCLISwarmUpDefaultPath(true, false, t)
|
testCLISwarmUpDefaultPath(true, false, t)
|
||||||
|
@ -284,8 +287,8 @@ func TestCLISwarmUpDefaultPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func testCLISwarmUpDefaultPath(toEncrypt bool, absDefaultPath bool, t *testing.T) {
|
func testCLISwarmUpDefaultPath(toEncrypt bool, absDefaultPath bool, t *testing.T) {
|
||||||
cluster := newTestCluster(t, 1)
|
srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil)
|
||||||
defer cluster.Shutdown()
|
defer srv.Close()
|
||||||
|
|
||||||
tmp, err := ioutil.TempDir("", "swarm-defaultpath-test")
|
tmp, err := ioutil.TempDir("", "swarm-defaultpath-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -309,7 +312,7 @@ func testCLISwarmUpDefaultPath(toEncrypt bool, absDefaultPath bool, t *testing.T
|
||||||
|
|
||||||
args := []string{
|
args := []string{
|
||||||
"--bzzapi",
|
"--bzzapi",
|
||||||
cluster.Nodes[0].URL,
|
srv.URL,
|
||||||
"--recursive",
|
"--recursive",
|
||||||
"--defaultpath",
|
"--defaultpath",
|
||||||
defaultPath,
|
defaultPath,
|
||||||
|
@ -326,7 +329,7 @@ func testCLISwarmUpDefaultPath(toEncrypt bool, absDefaultPath bool, t *testing.T
|
||||||
up.ExpectExit()
|
up.ExpectExit()
|
||||||
hash := matches[0]
|
hash := matches[0]
|
||||||
|
|
||||||
client := swarm.NewClient(cluster.Nodes[0].URL)
|
client := swarm.NewClient(srv.URL)
|
||||||
|
|
||||||
m, isEncrypted, err := client.DownloadManifest(hash)
|
m, isEncrypted, err := client.DownloadManifest(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -272,13 +272,13 @@ func ImportPreimages(db *ethdb.LDBDatabase, fn string) error {
|
||||||
// Accumulate the preimages and flush when enough ws gathered
|
// Accumulate the preimages and flush when enough ws gathered
|
||||||
preimages[crypto.Keccak256Hash(blob)] = common.CopyBytes(blob)
|
preimages[crypto.Keccak256Hash(blob)] = common.CopyBytes(blob)
|
||||||
if len(preimages) > 1024 {
|
if len(preimages) > 1024 {
|
||||||
rawdb.WritePreimages(db, 0, preimages)
|
rawdb.WritePreimages(db, preimages)
|
||||||
preimages = make(map[common.Hash][]byte)
|
preimages = make(map[common.Hash][]byte)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Flush the last batch preimage data
|
// Flush the last batch preimage data
|
||||||
if len(preimages) > 0 {
|
if len(preimages) > 0 {
|
||||||
rawdb.WritePreimages(db, 0, preimages)
|
rawdb.WritePreimages(db, preimages)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,8 +50,8 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/metrics/influxdb"
|
"github.com/ethereum/go-ethereum/metrics/influxdb"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||||
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
@ -744,9 +744,9 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
|
||||||
return // already set, don't apply defaults.
|
return // already set, don't apply defaults.
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.BootstrapNodes = make([]*discover.Node, 0, len(urls))
|
cfg.BootstrapNodes = make([]*enode.Node, 0, len(urls))
|
||||||
for _, url := range urls {
|
for _, url := range urls {
|
||||||
node, err := discover.ParseNode(url)
|
node, err := enode.ParseV4(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Crit("Bootstrap URL invalid", "enode", url, "err", err)
|
log.Crit("Bootstrap URL invalid", "enode", url, "err", err)
|
||||||
}
|
}
|
||||||
|
@ -1145,11 +1145,14 @@ func checkExclusive(ctx *cli.Context, args ...interface{}) {
|
||||||
if i+1 < len(args) {
|
if i+1 < len(args) {
|
||||||
switch option := args[i+1].(type) {
|
switch option := args[i+1].(type) {
|
||||||
case string:
|
case string:
|
||||||
// Extended flag, expand the name and shift the arguments
|
// Extended flag check, make sure value set doesn't conflict with passed in option
|
||||||
if ctx.GlobalString(flag.GetName()) == option {
|
if ctx.GlobalString(flag.GetName()) == option {
|
||||||
name += "=" + option
|
name += "=" + option
|
||||||
|
set = append(set, "--"+name)
|
||||||
}
|
}
|
||||||
|
// shift arguments and continue
|
||||||
i++
|
i++
|
||||||
|
continue
|
||||||
|
|
||||||
case cli.Flag:
|
case cli.Flag:
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -41,7 +41,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/p2p/nat"
|
"github.com/ethereum/go-ethereum/p2p/nat"
|
||||||
"github.com/ethereum/go-ethereum/whisper/mailserver"
|
"github.com/ethereum/go-ethereum/whisper/mailserver"
|
||||||
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
whisper "github.com/ethereum/go-ethereum/whisper/whisperv6"
|
||||||
|
@ -175,7 +175,7 @@ func initialize() {
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*argVerbosity), log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
|
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*argVerbosity), log.StreamHandler(os.Stderr, log.TerminalFormat(false))))
|
||||||
|
|
||||||
done = make(chan struct{})
|
done = make(chan struct{})
|
||||||
var peers []*discover.Node
|
var peers []*enode.Node
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if *generateKey {
|
if *generateKey {
|
||||||
|
@ -203,7 +203,7 @@ func initialize() {
|
||||||
if len(*argEnode) == 0 {
|
if len(*argEnode) == 0 {
|
||||||
argEnode = scanLineA("Please enter the peer's enode: ")
|
argEnode = scanLineA("Please enter the peer's enode: ")
|
||||||
}
|
}
|
||||||
peer := discover.MustParseNode(*argEnode)
|
peer := enode.MustParseV4(*argEnode)
|
||||||
peers = append(peers, peer)
|
peers = append(peers, peer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -747,11 +747,11 @@ func requestExpiredMessagesLoop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractIDFromEnode(s string) []byte {
|
func extractIDFromEnode(s string) []byte {
|
||||||
n, err := discover.ParseNode(s)
|
n, err := enode.ParseV4(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to parse enode: %s", err)
|
utils.Fatalf("Failed to parse enode: %s", err)
|
||||||
}
|
}
|
||||||
return n.ID[:]
|
return n.ID().Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// obfuscateBloom adds 16 random bits to the bloom
|
// obfuscateBloom adds 16 random bits to the bloom
|
||||||
|
|
|
@ -31,6 +31,15 @@ func ToHex(b []byte) string {
|
||||||
return "0x" + hex
|
return "0x" + hex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToHexArray creates a array of hex-string based on []byte
|
||||||
|
func ToHexArray(b [][]byte) []string {
|
||||||
|
r := make([]string, len(b))
|
||||||
|
for i := range b {
|
||||||
|
r[i] = ToHex(b[i])
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
// FromHex returns the bytes represented by the hexadecimal string s.
|
// FromHex returns the bytes represented by the hexadecimal string s.
|
||||||
// s may be prefixed with "0x".
|
// s may be prefixed with "0x".
|
||||||
func FromHex(s string) []byte {
|
func FromHex(s string) []byte {
|
||||||
|
|
|
@ -31,14 +31,15 @@ import (
|
||||||
|
|
||||||
var versionRegexp = regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`)
|
var versionRegexp = regexp.MustCompile(`([0-9]+)\.([0-9]+)\.([0-9]+)`)
|
||||||
|
|
||||||
// Contract contains information about a compiled contract, alongside its code.
|
// Contract contains information about a compiled contract, alongside its code and runtime code.
|
||||||
type Contract struct {
|
type Contract struct {
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
|
RuntimeCode string `json:"runtime-code"`
|
||||||
Info ContractInfo `json:"info"`
|
Info ContractInfo `json:"info"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContractInfo contains information about a compiled contract, including access
|
// ContractInfo contains information about a compiled contract, including access
|
||||||
// to the ABI definition, user and developer docs, and metadata.
|
// to the ABI definition, source mapping, user and developer docs, and metadata.
|
||||||
//
|
//
|
||||||
// Depending on the source, language version, compiler version, and compiler
|
// Depending on the source, language version, compiler version, and compiler
|
||||||
// options will provide information about how the contract was compiled.
|
// options will provide information about how the contract was compiled.
|
||||||
|
@ -48,6 +49,8 @@ type ContractInfo struct {
|
||||||
LanguageVersion string `json:"languageVersion"`
|
LanguageVersion string `json:"languageVersion"`
|
||||||
CompilerVersion string `json:"compilerVersion"`
|
CompilerVersion string `json:"compilerVersion"`
|
||||||
CompilerOptions string `json:"compilerOptions"`
|
CompilerOptions string `json:"compilerOptions"`
|
||||||
|
SrcMap string `json:"srcMap"`
|
||||||
|
SrcMapRuntime string `json:"srcMapRuntime"`
|
||||||
AbiDefinition interface{} `json:"abiDefinition"`
|
AbiDefinition interface{} `json:"abiDefinition"`
|
||||||
UserDoc interface{} `json:"userDoc"`
|
UserDoc interface{} `json:"userDoc"`
|
||||||
DeveloperDoc interface{} `json:"developerDoc"`
|
DeveloperDoc interface{} `json:"developerDoc"`
|
||||||
|
@ -63,14 +66,16 @@ type Solidity struct {
|
||||||
// --combined-output format
|
// --combined-output format
|
||||||
type solcOutput struct {
|
type solcOutput struct {
|
||||||
Contracts map[string]struct {
|
Contracts map[string]struct {
|
||||||
Bin, Abi, Devdoc, Userdoc, Metadata string
|
BinRuntime string `json:"bin-runtime"`
|
||||||
|
SrcMapRuntime string `json:"srcmap-runtime"`
|
||||||
|
Bin, SrcMap, Abi, Devdoc, Userdoc, Metadata string
|
||||||
}
|
}
|
||||||
Version string
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Solidity) makeArgs() []string {
|
func (s *Solidity) makeArgs() []string {
|
||||||
p := []string{
|
p := []string{
|
||||||
"--combined-json", "bin,abi,userdoc,devdoc",
|
"--combined-json", "bin,bin-runtime,srcmap,srcmap-runtime,abi,userdoc,devdoc",
|
||||||
"--optimize", // code optimizer switched on
|
"--optimize", // code optimizer switched on
|
||||||
}
|
}
|
||||||
if s.Major > 0 || s.Minor > 4 || s.Patch > 6 {
|
if s.Major > 0 || s.Minor > 4 || s.Patch > 6 {
|
||||||
|
@ -157,7 +162,7 @@ func (s *Solidity) run(cmd *exec.Cmd, source string) (map[string]*Contract, erro
|
||||||
// provided source, language and compiler version, and compiler options are all
|
// provided source, language and compiler version, and compiler options are all
|
||||||
// passed through into the Contract structs.
|
// passed through into the Contract structs.
|
||||||
//
|
//
|
||||||
// The solc output is expected to contain ABI, user docs, and dev docs.
|
// The solc output is expected to contain ABI, source mapping, user docs, and dev docs.
|
||||||
//
|
//
|
||||||
// Returns an error if the JSON is malformed or missing data, or if the JSON
|
// Returns an error if the JSON is malformed or missing data, or if the JSON
|
||||||
// embedded within the JSON is malformed.
|
// embedded within the JSON is malformed.
|
||||||
|
@ -185,12 +190,15 @@ func ParseCombinedJSON(combinedJSON []byte, source string, languageVersion strin
|
||||||
}
|
}
|
||||||
contracts[name] = &Contract{
|
contracts[name] = &Contract{
|
||||||
Code: "0x" + info.Bin,
|
Code: "0x" + info.Bin,
|
||||||
|
RuntimeCode: "0x" + info.BinRuntime,
|
||||||
Info: ContractInfo{
|
Info: ContractInfo{
|
||||||
Source: source,
|
Source: source,
|
||||||
Language: "Solidity",
|
Language: "Solidity",
|
||||||
LanguageVersion: languageVersion,
|
LanguageVersion: languageVersion,
|
||||||
CompilerVersion: compilerVersion,
|
CompilerVersion: compilerVersion,
|
||||||
CompilerOptions: compilerOptions,
|
CompilerOptions: compilerOptions,
|
||||||
|
SrcMap: info.SrcMap,
|
||||||
|
SrcMapRuntime: info.SrcMapRuntime,
|
||||||
AbiDefinition: abi,
|
AbiDefinition: abi,
|
||||||
UserDoc: userdoc,
|
UserDoc: userdoc,
|
||||||
DeveloperDoc: devdoc,
|
DeveloperDoc: devdoc,
|
||||||
|
|
|
@ -37,27 +37,28 @@ type API struct {
|
||||||
// result[0] - 32 bytes hex encoded current block header pow-hash
|
// result[0] - 32 bytes hex encoded current block header pow-hash
|
||||||
// result[1] - 32 bytes hex encoded seed hash used for DAG
|
// result[1] - 32 bytes hex encoded seed hash used for DAG
|
||||||
// result[2] - 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
// result[2] - 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
||||||
func (api *API) GetWork() ([3]string, error) {
|
// result[3] - hex encoded block number
|
||||||
|
func (api *API) GetWork() ([4]string, error) {
|
||||||
if api.ethash.config.PowMode != ModeNormal && api.ethash.config.PowMode != ModeTest {
|
if api.ethash.config.PowMode != ModeNormal && api.ethash.config.PowMode != ModeTest {
|
||||||
return [3]string{}, errors.New("not supported")
|
return [4]string{}, errors.New("not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
workCh = make(chan [3]string, 1)
|
workCh = make(chan [4]string, 1)
|
||||||
errc = make(chan error, 1)
|
errc = make(chan error, 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case api.ethash.fetchWorkCh <- &sealWork{errc: errc, res: workCh}:
|
case api.ethash.fetchWorkCh <- &sealWork{errc: errc, res: workCh}:
|
||||||
case <-api.ethash.exitCh:
|
case <-api.ethash.exitCh:
|
||||||
return [3]string{}, errEthashStopped
|
return [4]string{}, errEthashStopped
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case work := <-workCh:
|
case work := <-workCh:
|
||||||
return work, nil
|
return work, nil
|
||||||
case err := <-errc:
|
case err := <-errc:
|
||||||
return [3]string{}, err
|
return [4]string{}, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -432,7 +432,7 @@ type hashrate struct {
|
||||||
// sealWork wraps a seal work package for remote sealer.
|
// sealWork wraps a seal work package for remote sealer.
|
||||||
type sealWork struct {
|
type sealWork struct {
|
||||||
errc chan error
|
errc chan error
|
||||||
res chan [3]string
|
res chan [4]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ethash is a consensus engine based on proof-of-work implementing the ethash
|
// Ethash is a consensus engine based on proof-of-work implementing the ethash
|
||||||
|
|
|
@ -107,7 +107,7 @@ func TestRemoteSealer(t *testing.T) {
|
||||||
ethash.Seal(nil, block, results, nil)
|
ethash.Seal(nil, block, results, nil)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
work [3]string
|
work [4]string
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if work, err = api.GetWork(); err != nil || work[0] != sealhash.Hex() {
|
if work, err = api.GetWork(); err != nil || work[0] != sealhash.Hex() {
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
@ -193,7 +194,7 @@ func (ethash *Ethash) remote(notify []string, noverify bool) {
|
||||||
|
|
||||||
results chan<- *types.Block
|
results chan<- *types.Block
|
||||||
currentBlock *types.Block
|
currentBlock *types.Block
|
||||||
currentWork [3]string
|
currentWork [4]string
|
||||||
|
|
||||||
notifyTransport = &http.Transport{}
|
notifyTransport = &http.Transport{}
|
||||||
notifyClient = &http.Client{
|
notifyClient = &http.Client{
|
||||||
|
@ -234,12 +235,14 @@ func (ethash *Ethash) remote(notify []string, noverify bool) {
|
||||||
// result[0], 32 bytes hex encoded current block header pow-hash
|
// result[0], 32 bytes hex encoded current block header pow-hash
|
||||||
// result[1], 32 bytes hex encoded seed hash used for DAG
|
// result[1], 32 bytes hex encoded seed hash used for DAG
|
||||||
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
||||||
|
// result[3], hex encoded block number
|
||||||
makeWork := func(block *types.Block) {
|
makeWork := func(block *types.Block) {
|
||||||
hash := ethash.SealHash(block.Header())
|
hash := ethash.SealHash(block.Header())
|
||||||
|
|
||||||
currentWork[0] = hash.Hex()
|
currentWork[0] = hash.Hex()
|
||||||
currentWork[1] = common.BytesToHash(SeedHash(block.NumberU64())).Hex()
|
currentWork[1] = common.BytesToHash(SeedHash(block.NumberU64())).Hex()
|
||||||
currentWork[2] = common.BytesToHash(new(big.Int).Div(two256, block.Difficulty()).Bytes()).Hex()
|
currentWork[2] = common.BytesToHash(new(big.Int).Div(two256, block.Difficulty()).Bytes()).Hex()
|
||||||
|
currentWork[3] = hexutil.EncodeBig(block.Number())
|
||||||
|
|
||||||
// Trace the seal work fetched by remote sealer.
|
// Trace the seal work fetched by remote sealer.
|
||||||
currentBlock = block
|
currentBlock = block
|
||||||
|
|
|
@ -151,6 +151,38 @@ func (self *ENS) Resolve(name string) (common.Hash, error) {
|
||||||
return common.BytesToHash(ret[:]), nil
|
return common.BytesToHash(ret[:]), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Addr is a non-transactional call that returns the address associated with a name.
|
||||||
|
func (self *ENS) Addr(name string) (common.Address, error) {
|
||||||
|
node := EnsNode(name)
|
||||||
|
|
||||||
|
resolver, err := self.getResolver(node)
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := resolver.Addr(node)
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return common.BytesToAddress(ret[:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAddress sets the address associated with a name. Only works if the caller
|
||||||
|
// owns the name, and the associated resolver implements a `setAddress` function.
|
||||||
|
func (self *ENS) SetAddr(name string, addr common.Address) (*types.Transaction, error) {
|
||||||
|
node := EnsNode(name)
|
||||||
|
|
||||||
|
resolver, err := self.getResolver(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := self.TransactOpts
|
||||||
|
opts.GasLimit = 200000
|
||||||
|
return resolver.Contract.SetAddr(&opts, node, addr)
|
||||||
|
}
|
||||||
|
|
||||||
// Register registers a new domain name for the caller, making them the owner of the new name.
|
// Register registers a new domain name for the caller, making them the owner of the new name.
|
||||||
// Only works if the registrar for the parent domain implements the FIFS registrar protocol.
|
// Only works if the registrar for the parent domain implements the FIFS registrar protocol.
|
||||||
func (self *ENS) Register(name string) (*types.Transaction, error) {
|
func (self *ENS) Register(name string) (*types.Transaction, error) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/contracts/ens/contract"
|
"github.com/ethereum/go-ethereum/contracts/ens/contract"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
@ -32,6 +33,7 @@ var (
|
||||||
name = "my name on ENS"
|
name = "my name on ENS"
|
||||||
hash = crypto.Keccak256Hash([]byte("my content"))
|
hash = crypto.Keccak256Hash([]byte("my content"))
|
||||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
addr = crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
testAddr = common.HexToAddress("0x1234123412341234123412341234123412341234")
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestENS(t *testing.T) {
|
func TestENS(t *testing.T) {
|
||||||
|
@ -74,4 +76,19 @@ func TestENS(t *testing.T) {
|
||||||
if vhost != hash {
|
if vhost != hash {
|
||||||
t.Fatalf("resolve error, expected %v, got %v", hash.Hex(), vhost.Hex())
|
t.Fatalf("resolve error, expected %v, got %v", hash.Hex(), vhost.Hex())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set the address for the name
|
||||||
|
if _, err = ens.SetAddr(name, testAddr); err != nil {
|
||||||
|
t.Fatalf("can't set address: %v", err)
|
||||||
|
}
|
||||||
|
contractBackend.Commit()
|
||||||
|
|
||||||
|
// Try to resolve the name to an address
|
||||||
|
recoveredAddr, err := ens.Addr(name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
if vhost != hash {
|
||||||
|
t.Fatalf("resolve error, expected %v, got %v", testAddr.Hex(), recoveredAddr.Hex())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,9 +109,9 @@ func PrintDisassembled(code string) error {
|
||||||
it := NewInstructionIterator(script)
|
it := NewInstructionIterator(script)
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
if it.Arg() != nil && 0 < len(it.Arg()) {
|
if it.Arg() != nil && 0 < len(it.Arg()) {
|
||||||
fmt.Printf("%06v: %v 0x%x\n", it.PC(), it.Op(), it.Arg())
|
fmt.Printf("%05x: %v 0x%x\n", it.PC(), it.Op(), it.Arg())
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%06v: %v\n", it.PC(), it.Op())
|
fmt.Printf("%05x: %v\n", it.PC(), it.Op())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return it.Error()
|
return it.Error()
|
||||||
|
@ -124,9 +124,9 @@ func Disassemble(script []byte) ([]string, error) {
|
||||||
it := NewInstructionIterator(script)
|
it := NewInstructionIterator(script)
|
||||||
for it.Next() {
|
for it.Next() {
|
||||||
if it.Arg() != nil && 0 < len(it.Arg()) {
|
if it.Arg() != nil && 0 < len(it.Arg()) {
|
||||||
instrs = append(instrs, fmt.Sprintf("%06v: %v 0x%x\n", it.PC(), it.Op(), it.Arg()))
|
instrs = append(instrs, fmt.Sprintf("%05x: %v 0x%x\n", it.PC(), it.Op(), it.Arg()))
|
||||||
} else {
|
} else {
|
||||||
instrs = append(instrs, fmt.Sprintf("%06v: %v\n", it.PC(), it.Op()))
|
instrs = append(instrs, fmt.Sprintf("%05x: %v\n", it.PC(), it.Op()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := it.Error(); err != nil {
|
if err := it.Error(); err != nil {
|
||||||
|
|
|
@ -53,12 +53,6 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||||
if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) {
|
if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) {
|
||||||
return ErrKnownBlock
|
return ErrKnownBlock
|
||||||
}
|
}
|
||||||
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
|
||||||
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
|
|
||||||
return consensus.ErrUnknownAncestor
|
|
||||||
}
|
|
||||||
return consensus.ErrPrunedAncestor
|
|
||||||
}
|
|
||||||
// Header validity is known at this point, check the uncles and transactions
|
// Header validity is known at this point, check the uncles and transactions
|
||||||
header := block.Header()
|
header := block.Header()
|
||||||
if err := v.engine.VerifyUncles(v.bc, block); err != nil {
|
if err := v.engine.VerifyUncles(v.bc, block); err != nil {
|
||||||
|
@ -70,6 +64,12 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
|
||||||
if hash := types.DeriveSha(block.Transactions()); hash != header.TxHash {
|
if hash := types.DeriveSha(block.Transactions()); hash != header.TxHash {
|
||||||
return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash)
|
return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash)
|
||||||
}
|
}
|
||||||
|
if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) {
|
||||||
|
if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) {
|
||||||
|
return consensus.ErrUnknownAncestor
|
||||||
|
}
|
||||||
|
return consensus.ErrPrunedAncestor
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,7 @@ var (
|
||||||
const (
|
const (
|
||||||
bodyCacheLimit = 256
|
bodyCacheLimit = 256
|
||||||
blockCacheLimit = 256
|
blockCacheLimit = 256
|
||||||
|
receiptsCacheLimit = 32
|
||||||
maxFutureBlocks = 256
|
maxFutureBlocks = 256
|
||||||
maxTimeFutureBlocks = 30
|
maxTimeFutureBlocks = 30
|
||||||
badBlockLimit = 10
|
badBlockLimit = 10
|
||||||
|
@ -115,6 +116,7 @@ type BlockChain struct {
|
||||||
stateCache state.Database // State database to reuse between imports (contains state cache)
|
stateCache state.Database // State database to reuse between imports (contains state cache)
|
||||||
bodyCache *lru.Cache // Cache for the most recent block bodies
|
bodyCache *lru.Cache // Cache for the most recent block bodies
|
||||||
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
|
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
|
||||||
|
receiptsCache *lru.Cache // Cache for the most recent receipts per block
|
||||||
blockCache *lru.Cache // Cache for the most recent entire blocks
|
blockCache *lru.Cache // Cache for the most recent entire blocks
|
||||||
futureBlocks *lru.Cache // future blocks are blocks added for later processing
|
futureBlocks *lru.Cache // future blocks are blocks added for later processing
|
||||||
|
|
||||||
|
@ -141,12 +143,13 @@ type BlockChain struct {
|
||||||
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool) (*BlockChain, error) {
|
func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *params.ChainConfig, engine consensus.Engine, vmConfig vm.Config, shouldPreserve func(block *types.Block) bool) (*BlockChain, error) {
|
||||||
if cacheConfig == nil {
|
if cacheConfig == nil {
|
||||||
cacheConfig = &CacheConfig{
|
cacheConfig = &CacheConfig{
|
||||||
TrieNodeLimit: 256 * 1024 * 1024,
|
TrieNodeLimit: 256,
|
||||||
TrieTimeLimit: 5 * time.Minute,
|
TrieTimeLimit: 5 * time.Minute,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bodyCache, _ := lru.New(bodyCacheLimit)
|
bodyCache, _ := lru.New(bodyCacheLimit)
|
||||||
bodyRLPCache, _ := lru.New(bodyCacheLimit)
|
bodyRLPCache, _ := lru.New(bodyCacheLimit)
|
||||||
|
receiptsCache, _ := lru.New(receiptsCacheLimit)
|
||||||
blockCache, _ := lru.New(blockCacheLimit)
|
blockCache, _ := lru.New(blockCacheLimit)
|
||||||
futureBlocks, _ := lru.New(maxFutureBlocks)
|
futureBlocks, _ := lru.New(maxFutureBlocks)
|
||||||
badBlocks, _ := lru.New(badBlockLimit)
|
badBlocks, _ := lru.New(badBlockLimit)
|
||||||
|
@ -161,6 +164,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
|
||||||
shouldPreserve: shouldPreserve,
|
shouldPreserve: shouldPreserve,
|
||||||
bodyCache: bodyCache,
|
bodyCache: bodyCache,
|
||||||
bodyRLPCache: bodyRLPCache,
|
bodyRLPCache: bodyRLPCache,
|
||||||
|
receiptsCache: receiptsCache,
|
||||||
blockCache: blockCache,
|
blockCache: blockCache,
|
||||||
futureBlocks: futureBlocks,
|
futureBlocks: futureBlocks,
|
||||||
engine: engine,
|
engine: engine,
|
||||||
|
@ -292,6 +296,7 @@ func (bc *BlockChain) SetHead(head uint64) error {
|
||||||
// Clear out any stale content from the caches
|
// Clear out any stale content from the caches
|
||||||
bc.bodyCache.Purge()
|
bc.bodyCache.Purge()
|
||||||
bc.bodyRLPCache.Purge()
|
bc.bodyRLPCache.Purge()
|
||||||
|
bc.receiptsCache.Purge()
|
||||||
bc.blockCache.Purge()
|
bc.blockCache.Purge()
|
||||||
bc.futureBlocks.Purge()
|
bc.futureBlocks.Purge()
|
||||||
|
|
||||||
|
@ -631,11 +636,18 @@ func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block {
|
||||||
|
|
||||||
// GetReceiptsByHash retrieves the receipts for all transactions in a given block.
|
// GetReceiptsByHash retrieves the receipts for all transactions in a given block.
|
||||||
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
|
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {
|
||||||
|
if receipts, ok := bc.receiptsCache.Get(hash); ok {
|
||||||
|
return receipts.(types.Receipts)
|
||||||
|
}
|
||||||
|
|
||||||
number := rawdb.ReadHeaderNumber(bc.db, hash)
|
number := rawdb.ReadHeaderNumber(bc.db, hash)
|
||||||
if number == nil {
|
if number == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return rawdb.ReadReceipts(bc.db, hash, *number)
|
|
||||||
|
receipts := rawdb.ReadReceipts(bc.db, hash, *number)
|
||||||
|
bc.receiptsCache.Add(hash, receipts)
|
||||||
|
return receipts
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors.
|
// GetBlocksFromHash returns the block corresponding to hash and up to n-1 ancestors.
|
||||||
|
@ -1032,7 +1044,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
|
||||||
}
|
}
|
||||||
// Write the positional metadata for transaction/receipt lookups and preimages
|
// Write the positional metadata for transaction/receipt lookups and preimages
|
||||||
rawdb.WriteTxLookupEntries(batch, block)
|
rawdb.WriteTxLookupEntries(batch, block)
|
||||||
rawdb.WritePreimages(batch, block.NumberU64(), state.Preimages())
|
rawdb.WritePreimages(batch, state.Preimages())
|
||||||
|
|
||||||
status = CanonStatTy
|
status = CanonStatTy
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -53,14 +53,14 @@ type ChainIndexerChain interface {
|
||||||
// CurrentHeader retrieves the latest locally known header.
|
// CurrentHeader retrieves the latest locally known header.
|
||||||
CurrentHeader() *types.Header
|
CurrentHeader() *types.Header
|
||||||
|
|
||||||
// SubscribeChainEvent subscribes to new head header notifications.
|
// SubscribeChainHeadEvent subscribes to new head header notifications.
|
||||||
SubscribeChainEvent(ch chan<- ChainEvent) event.Subscription
|
SubscribeChainHeadEvent(ch chan<- ChainHeadEvent) event.Subscription
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChainIndexer does a post-processing job for equally sized sections of the
|
// ChainIndexer does a post-processing job for equally sized sections of the
|
||||||
// canonical chain (like BlooomBits and CHT structures). A ChainIndexer is
|
// canonical chain (like BlooomBits and CHT structures). A ChainIndexer is
|
||||||
// connected to the blockchain through the event system by starting a
|
// connected to the blockchain through the event system by starting a
|
||||||
// ChainEventLoop in a goroutine.
|
// ChainHeadEventLoop in a goroutine.
|
||||||
//
|
//
|
||||||
// Further child ChainIndexers can be added which use the output of the parent
|
// Further child ChainIndexers can be added which use the output of the parent
|
||||||
// section indexer. These child indexers receive new head notifications only
|
// section indexer. These child indexers receive new head notifications only
|
||||||
|
@ -142,8 +142,8 @@ func (c *ChainIndexer) AddCheckpoint(section uint64, shead common.Hash) {
|
||||||
// cascading background processing. Children do not need to be started, they
|
// cascading background processing. Children do not need to be started, they
|
||||||
// are notified about new events by their parents.
|
// are notified about new events by their parents.
|
||||||
func (c *ChainIndexer) Start(chain ChainIndexerChain) {
|
func (c *ChainIndexer) Start(chain ChainIndexerChain) {
|
||||||
events := make(chan ChainEvent, 10)
|
events := make(chan ChainHeadEvent, 10)
|
||||||
sub := chain.SubscribeChainEvent(events)
|
sub := chain.SubscribeChainHeadEvent(events)
|
||||||
|
|
||||||
go c.eventLoop(chain.CurrentHeader(), events, sub)
|
go c.eventLoop(chain.CurrentHeader(), events, sub)
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,7 @@ func (c *ChainIndexer) Close() error {
|
||||||
// eventLoop is a secondary - optional - event loop of the indexer which is only
|
// eventLoop is a secondary - optional - event loop of the indexer which is only
|
||||||
// started for the outermost indexer to push chain head events into a processing
|
// started for the outermost indexer to push chain head events into a processing
|
||||||
// queue.
|
// queue.
|
||||||
func (c *ChainIndexer) eventLoop(currentHeader *types.Header, events chan ChainEvent, sub event.Subscription) {
|
func (c *ChainIndexer) eventLoop(currentHeader *types.Header, events chan ChainHeadEvent, sub event.Subscription) {
|
||||||
// Mark the chain indexer as active, requiring an additional teardown
|
// Mark the chain indexer as active, requiring an additional teardown
|
||||||
atomic.StoreUint32(&c.active, 1)
|
atomic.StoreUint32(&c.active, 1)
|
||||||
|
|
||||||
|
@ -219,15 +219,15 @@ func (c *ChainIndexer) eventLoop(currentHeader *types.Header, events chan ChainE
|
||||||
}
|
}
|
||||||
header := ev.Block.Header()
|
header := ev.Block.Header()
|
||||||
if header.ParentHash != prevHash {
|
if header.ParentHash != prevHash {
|
||||||
// Reorg to the common ancestor (might not exist in light sync mode, skip reorg then)
|
// Reorg to the common ancestor if needed (might not exist in light sync mode, skip reorg then)
|
||||||
// TODO(karalabe, zsfelfoldi): This seems a bit brittle, can we detect this case explicitly?
|
// TODO(karalabe, zsfelfoldi): This seems a bit brittle, can we detect this case explicitly?
|
||||||
|
|
||||||
// TODO(karalabe): This operation is expensive and might block, causing the event system to
|
if rawdb.ReadCanonicalHash(c.chainDb, prevHeader.Number.Uint64()) != prevHash {
|
||||||
// potentially also lock up. We need to do with on a different thread somehow.
|
|
||||||
if h := rawdb.FindCommonAncestor(c.chainDb, prevHeader, header); h != nil {
|
if h := rawdb.FindCommonAncestor(c.chainDb, prevHeader, header); h != nil {
|
||||||
c.newHead(h.Number.Uint64(), true)
|
c.newHead(h.Number.Uint64(), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
c.newHead(header.Number.Uint64(), false)
|
c.newHead(header.Number.Uint64(), false)
|
||||||
|
|
||||||
prevHeader, prevHash = header, header.Hash()
|
prevHeader, prevHash = header, header.Hash()
|
||||||
|
|
|
@ -36,7 +36,6 @@ type BlockGen struct {
|
||||||
i int
|
i int
|
||||||
parent *types.Block
|
parent *types.Block
|
||||||
chain []*types.Block
|
chain []*types.Block
|
||||||
chainReader consensus.ChainReader
|
|
||||||
header *types.Header
|
header *types.Header
|
||||||
statedb *state.StateDB
|
statedb *state.StateDB
|
||||||
|
|
||||||
|
@ -138,7 +137,7 @@ func (b *BlockGen) AddUncle(h *types.Header) {
|
||||||
// For index -1, PrevBlock returns the parent block given to GenerateChain.
|
// For index -1, PrevBlock returns the parent block given to GenerateChain.
|
||||||
func (b *BlockGen) PrevBlock(index int) *types.Block {
|
func (b *BlockGen) PrevBlock(index int) *types.Block {
|
||||||
if index >= b.i {
|
if index >= b.i {
|
||||||
panic("block index out of range")
|
panic(fmt.Errorf("block index %d out of range (%d,%d)", index, -1, b.i))
|
||||||
}
|
}
|
||||||
if index == -1 {
|
if index == -1 {
|
||||||
return b.parent
|
return b.parent
|
||||||
|
@ -154,7 +153,8 @@ func (b *BlockGen) OffsetTime(seconds int64) {
|
||||||
if b.header.Time.Cmp(b.parent.Header().Time) <= 0 {
|
if b.header.Time.Cmp(b.parent.Header().Time) <= 0 {
|
||||||
panic("block time out of range")
|
panic("block time out of range")
|
||||||
}
|
}
|
||||||
b.header.Difficulty = b.engine.CalcDifficulty(b.chainReader, b.header.Time.Uint64(), b.parent.Header())
|
chainreader := &fakeChainReader{config: b.config}
|
||||||
|
b.header.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time.Uint64(), b.parent.Header())
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateChain creates a chain of n blocks. The first block's
|
// GenerateChain creates a chain of n blocks. The first block's
|
||||||
|
@ -174,14 +174,10 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
||||||
config = params.TestChainConfig
|
config = params.TestChainConfig
|
||||||
}
|
}
|
||||||
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
|
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
|
||||||
|
chainreader := &fakeChainReader{config: config}
|
||||||
genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
|
genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
|
||||||
// TODO(karalabe): This is needed for clique, which depends on multiple blocks.
|
b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine}
|
||||||
// It's nonetheless ugly to spin up a blockchain here. Get rid of this somehow.
|
b.header = makeHeader(chainreader, parent, statedb, b.engine)
|
||||||
blockchain, _ := NewBlockChain(db, nil, config, engine, vm.Config{}, nil)
|
|
||||||
defer blockchain.Stop()
|
|
||||||
|
|
||||||
b := &BlockGen{i: i, parent: parent, chain: blocks, chainReader: blockchain, statedb: statedb, config: config, engine: engine}
|
|
||||||
b.header = makeHeader(b.chainReader, parent, statedb, b.engine)
|
|
||||||
|
|
||||||
// Mutate the state and block according to any hard-fork specs
|
// Mutate the state and block according to any hard-fork specs
|
||||||
if daoBlock := config.DAOForkBlock; daoBlock != nil {
|
if daoBlock := config.DAOForkBlock; daoBlock != nil {
|
||||||
|
@ -201,7 +197,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
||||||
}
|
}
|
||||||
if b.engine != nil {
|
if b.engine != nil {
|
||||||
// Finalize and seal the block
|
// Finalize and seal the block
|
||||||
block, _ := b.engine.Finalize(b.chainReader, b.header, statedb, b.txs, b.uncles, b.receipts)
|
block, _ := b.engine.Finalize(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts)
|
||||||
|
|
||||||
// Write state changes to db
|
// Write state changes to db
|
||||||
root, err := statedb.Commit(config.IsEIP158(b.header.Number))
|
root, err := statedb.Commit(config.IsEIP158(b.header.Number))
|
||||||
|
@ -269,3 +265,19 @@ func makeBlockChain(parent *types.Block, n int, engine consensus.Engine, db ethd
|
||||||
})
|
})
|
||||||
return blocks
|
return blocks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fakeChainReader struct {
|
||||||
|
config *params.ChainConfig
|
||||||
|
genesis *types.Block
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config returns the chain configuration.
|
||||||
|
func (cr *fakeChainReader) Config() *params.ChainConfig {
|
||||||
|
return cr.config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cr *fakeChainReader) CurrentHeader() *types.Header { return nil }
|
||||||
|
func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header { return nil }
|
||||||
|
func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil }
|
||||||
|
func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil }
|
||||||
|
func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil }
|
||||||
|
|
|
@ -278,7 +278,7 @@ func ReadReceipts(db DatabaseReader, hash common.Hash, number uint64) types.Rece
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Convert the revceipts from their storage form to their internal representation
|
// Convert the receipts from their storage form to their internal representation
|
||||||
storageReceipts := []*types.ReceiptForStorage{}
|
storageReceipts := []*types.ReceiptForStorage{}
|
||||||
if err := rlp.DecodeBytes(data, &storageReceipts); err != nil {
|
if err := rlp.DecodeBytes(data, &storageReceipts); err != nil {
|
||||||
log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
|
log.Error("Invalid receipt array RLP", "hash", hash, "err", err)
|
||||||
|
|
|
@ -77,9 +77,8 @@ func ReadPreimage(db DatabaseReader, hash common.Hash) []byte {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// WritePreimages writes the provided set of preimages to the database. `number` is the
|
// WritePreimages writes the provided set of preimages to the database.
|
||||||
// current block number, and is used for debug messages only.
|
func WritePreimages(db DatabaseWriter, preimages map[common.Hash][]byte) {
|
||||||
func WritePreimages(db DatabaseWriter, number uint64, preimages map[common.Hash][]byte) {
|
|
||||||
for hash, preimage := range preimages {
|
for hash, preimage := range preimages {
|
||||||
if err := db.Put(preimageKey(hash), preimage); err != nil {
|
if err := db.Put(preimageKey(hash), preimage); err != nil {
|
||||||
log.Crit("Failed to store trie preimage", "err", err)
|
log.Crit("Failed to store trie preimage", "err", err)
|
||||||
|
|
|
@ -35,7 +35,7 @@ var (
|
||||||
// headBlockKey tracks the latest know full block's hash.
|
// headBlockKey tracks the latest know full block's hash.
|
||||||
headBlockKey = []byte("LastBlock")
|
headBlockKey = []byte("LastBlock")
|
||||||
|
|
||||||
// headFastBlockKey tracks the latest known incomplete block's hash duirng fast sync.
|
// headFastBlockKey tracks the latest known incomplete block's hash during fast sync.
|
||||||
headFastBlockKey = []byte("LastFast")
|
headFastBlockKey = []byte("LastFast")
|
||||||
|
|
||||||
// fastTrieProgressKey tracks the number of trie entries imported during fast sync.
|
// fastTrieProgressKey tracks the number of trie entries imported during fast sync.
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
@ -44,6 +44,13 @@ var (
|
||||||
emptyCode = crypto.Keccak256Hash(nil)
|
emptyCode = crypto.Keccak256Hash(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type proofList [][]byte
|
||||||
|
|
||||||
|
func (n *proofList) Put(key []byte, value []byte) error {
|
||||||
|
*n = append(*n, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// StateDBs within the ethereum protocol are used to store anything
|
// StateDBs within the ethereum protocol are used to store anything
|
||||||
// within the merkle trie. StateDBs take care of caching and storing
|
// within the merkle trie. StateDBs take care of caching and storing
|
||||||
// nested states. It's the general query interface to retrieve:
|
// nested states. It's the general query interface to retrieve:
|
||||||
|
@ -79,8 +86,6 @@ type StateDB struct {
|
||||||
journal *journal
|
journal *journal
|
||||||
validRevisions []revision
|
validRevisions []revision
|
||||||
nextRevisionId int
|
nextRevisionId int
|
||||||
|
|
||||||
lock sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new state from a given trie.
|
// Create a new state from a given trie.
|
||||||
|
@ -256,6 +261,24 @@ func (self *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProof returns the MerkleProof for a given Account
|
||||||
|
func (self *StateDB) GetProof(a common.Address) ([][]byte, error) {
|
||||||
|
var proof proofList
|
||||||
|
err := self.trie.Prove(crypto.Keccak256(a.Bytes()), 0, &proof)
|
||||||
|
return [][]byte(proof), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProof returns the StorageProof for given key
|
||||||
|
func (self *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) {
|
||||||
|
var proof proofList
|
||||||
|
trie := self.StorageTrie(a)
|
||||||
|
if trie == nil {
|
||||||
|
return proof, errors.New("storage trie for requested address does not exist")
|
||||||
|
}
|
||||||
|
err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof)
|
||||||
|
return [][]byte(proof), err
|
||||||
|
}
|
||||||
|
|
||||||
// GetCommittedState retrieves a value from the given account's committed storage trie.
|
// GetCommittedState retrieves a value from the given account's committed storage trie.
|
||||||
func (self *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
func (self *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
|
||||||
stateObject := self.getStateObject(addr)
|
stateObject := self.getStateObject(addr)
|
||||||
|
@ -479,9 +502,6 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common
|
||||||
// Copy creates a deep, independent copy of the state.
|
// Copy creates a deep, independent copy of the state.
|
||||||
// Snapshots of the copied state cannot be applied to the copy.
|
// Snapshots of the copied state cannot be applied to the copy.
|
||||||
func (self *StateDB) Copy() *StateDB {
|
func (self *StateDB) Copy() *StateDB {
|
||||||
self.lock.Lock()
|
|
||||||
defer self.lock.Unlock()
|
|
||||||
|
|
||||||
// Copy all the basic fields, initialize the memory ones
|
// Copy all the basic fields, initialize the memory ones
|
||||||
state := &StateDB{
|
state := &StateDB{
|
||||||
db: self.db,
|
db: self.db,
|
||||||
|
|
|
@ -47,7 +47,7 @@ type Log struct {
|
||||||
TxIndex uint `json:"transactionIndex" gencodec:"required"`
|
TxIndex uint `json:"transactionIndex" gencodec:"required"`
|
||||||
// hash of the block in which the transaction was included
|
// hash of the block in which the transaction was included
|
||||||
BlockHash common.Hash `json:"blockHash"`
|
BlockHash common.Hash `json:"blockHash"`
|
||||||
// index of the log in the receipt
|
// index of the log in the block
|
||||||
Index uint `json:"logIndex" gencodec:"required"`
|
Index uint `json:"logIndex" gencodec:"required"`
|
||||||
|
|
||||||
// The Removed field is true if this log was reverted due to a chain reorganisation.
|
// The Removed field is true if this log was reverted due to a chain reorganisation.
|
||||||
|
|
|
@ -165,6 +165,9 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
|
||||||
if err := dec.UnmarshalJSON(input); err != nil {
|
if err := dec.UnmarshalJSON(input); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withSignature := dec.V.Sign() != 0 || dec.R.Sign() != 0 || dec.S.Sign() != 0
|
||||||
|
if withSignature {
|
||||||
var V byte
|
var V byte
|
||||||
if isProtectedV(dec.V) {
|
if isProtectedV(dec.V) {
|
||||||
chainID := deriveChainId(dec.V).Uint64()
|
chainID := deriveChainId(dec.V).Uint64()
|
||||||
|
@ -175,6 +178,8 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
|
||||||
if !crypto.ValidateSignatureValues(V, dec.R, dec.S, false) {
|
if !crypto.ValidateSignatureValues(V, dec.R, dec.S, false) {
|
||||||
return ErrInvalidSig
|
return ErrInvalidSig
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
*tx = Transaction{data: dec}
|
*tx = Transaction{data: dec}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,7 +139,7 @@ func (s EIP155Signer) Sender(tx *Transaction) (common.Address, error) {
|
||||||
return recoverPlain(s.Hash(tx), tx.data.R, tx.data.S, V, true, tx.IsPrivate())
|
return recoverPlain(s.Hash(tx), tx.data.R, tx.data.S, V, true, tx.IsPrivate())
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSignature returns a new transaction with the given signature. This signature
|
// SignatureValues returns signature values. This signature
|
||||||
// needs to be in the [R || S || V] format where V is 0 or 1.
|
// needs to be in the [R || S || V] format where V is 0 or 1.
|
||||||
func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
|
func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
|
||||||
if tx.IsPrivate() {
|
if tx.IsPrivate() {
|
||||||
|
@ -244,13 +244,13 @@ func recoverPlain(sighash common.Hash, R, S, Vb *big.Int, homestead bool, isPriv
|
||||||
if !crypto.ValidateSignatureValues(V, R, S, homestead) {
|
if !crypto.ValidateSignatureValues(V, R, S, homestead) {
|
||||||
return common.Address{}, ErrInvalidSig
|
return common.Address{}, ErrInvalidSig
|
||||||
}
|
}
|
||||||
// encode the snature in uncompressed format
|
// encode the signature in uncompressed format
|
||||||
r, s := R.Bytes(), S.Bytes()
|
r, s := R.Bytes(), S.Bytes()
|
||||||
sig := make([]byte, 65)
|
sig := make([]byte, 65)
|
||||||
copy(sig[32-len(r):32], r)
|
copy(sig[32-len(r):32], r)
|
||||||
copy(sig[64-len(s):64], s)
|
copy(sig[64-len(s):64], s)
|
||||||
sig[64] = V
|
sig[64] = V
|
||||||
// recover the public key from the snature
|
// recover the public key from the signature
|
||||||
pub, err := crypto.Ecrecover(sighash[:], sig)
|
pub, err := crypto.Ecrecover(sighash[:], sig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
|
|
|
@ -209,6 +209,7 @@ func TestTransactionJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
signer := NewEIP155Signer(common.Big1)
|
signer := NewEIP155Signer(common.Big1)
|
||||||
|
|
||||||
|
transactions := make([]*Transaction, 0, 50)
|
||||||
for i := uint64(0); i < 25; i++ {
|
for i := uint64(0); i < 25; i++ {
|
||||||
var tx *Transaction
|
var tx *Transaction
|
||||||
switch i % 2 {
|
switch i % 2 {
|
||||||
|
@ -217,20 +218,25 @@ func TestTransactionJSON(t *testing.T) {
|
||||||
case 1:
|
case 1:
|
||||||
tx = NewContractCreation(i, common.Big0, 1, common.Big2, []byte("abcdef"))
|
tx = NewContractCreation(i, common.Big0, 1, common.Big2, []byte("abcdef"))
|
||||||
}
|
}
|
||||||
|
transactions = append(transactions, tx)
|
||||||
|
|
||||||
tx, err := SignTx(tx, signer, key)
|
signedTx, err := SignTx(tx, signer, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not sign transaction: %v", err)
|
t.Fatalf("could not sign transaction: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transactions = append(transactions, signedTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tx := range transactions {
|
||||||
data, err := json.Marshal(tx)
|
data, err := json.Marshal(tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("json.Marshal failed: %v", err)
|
t.Fatalf("json.Marshal failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsedTx *Transaction
|
var parsedTx *Transaction
|
||||||
if err := json.Unmarshal(data, &parsedTx); err != nil {
|
if err := json.Unmarshal(data, &parsedTx); err != nil {
|
||||||
t.Errorf("json.Unmarshal failed: %v", err)
|
t.Fatalf("json.Unmarshal failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// compare nonce, price, gaslimit, recipient, amount, payload, V, R, S
|
// compare nonce, price, gaslimit, recipient, amount, payload, V, R, S
|
||||||
|
|
|
@ -16,34 +16,6 @@
|
||||||
|
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
// destinations stores one map per contract (keyed by hash of code).
|
|
||||||
// The maps contain an entry for each location of a JUMPDEST
|
|
||||||
// instruction.
|
|
||||||
type destinations map[common.Hash]bitvec
|
|
||||||
|
|
||||||
// has checks whether code has a JUMPDEST at dest.
|
|
||||||
func (d destinations) has(codehash common.Hash, code []byte, dest *big.Int) bool {
|
|
||||||
// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
|
|
||||||
// Don't bother checking for JUMPDEST in that case.
|
|
||||||
udest := dest.Uint64()
|
|
||||||
if dest.BitLen() >= 63 || udest >= uint64(len(code)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
m, analysed := d[codehash]
|
|
||||||
if !analysed {
|
|
||||||
m = codeBitmap(code)
|
|
||||||
d[codehash] = m
|
|
||||||
}
|
|
||||||
return OpCode(code[udest]) == JUMPDEST && m.codeSegment(udest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bitvec is a bit vector which maps bytes in a program.
|
// bitvec is a bit vector which maps bytes in a program.
|
||||||
// An unset bit means the byte is an opcode, a set bit means
|
// An unset bit means the byte is an opcode, a set bit means
|
||||||
// it's data (i.e. argument of PUSHxx).
|
// it's data (i.e. argument of PUSHxx).
|
||||||
|
|
|
@ -16,7 +16,11 @@
|
||||||
|
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
func TestJumpDestAnalysis(t *testing.T) {
|
func TestJumpDestAnalysis(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -49,5 +53,23 @@ func TestJumpDestAnalysis(t *testing.T) {
|
||||||
t.Fatalf("expected %x, got %02x", test.exp, ret[test.which])
|
t.Fatalf("expected %x, got %02x", test.exp, ret[test.which])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkJumpdestAnalysis_1200k(bench *testing.B) {
|
||||||
|
// 1.4 ms
|
||||||
|
code := make([]byte, 1200000)
|
||||||
|
bench.ResetTimer()
|
||||||
|
for i := 0; i < bench.N; i++ {
|
||||||
|
codeBitmap(code)
|
||||||
|
}
|
||||||
|
bench.StopTimer()
|
||||||
|
}
|
||||||
|
func BenchmarkJumpdestHashing_1200k(bench *testing.B) {
|
||||||
|
// 4 ms
|
||||||
|
code := make([]byte, 1200000)
|
||||||
|
bench.ResetTimer()
|
||||||
|
for i := 0; i < bench.N; i++ {
|
||||||
|
crypto.Keccak256Hash(code)
|
||||||
|
}
|
||||||
|
bench.StopTimer()
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,8 @@ type Contract struct {
|
||||||
caller ContractRef
|
caller ContractRef
|
||||||
self ContractRef
|
self ContractRef
|
||||||
|
|
||||||
jumpdests destinations // result of JUMPDEST analysis.
|
jumpdests map[common.Hash]bitvec // Aggregated result of JUMPDEST analysis.
|
||||||
|
analysis bitvec // Locally cached result of JUMPDEST analysis
|
||||||
|
|
||||||
Code []byte
|
Code []byte
|
||||||
CodeHash common.Hash
|
CodeHash common.Hash
|
||||||
|
@ -58,21 +59,17 @@ type Contract struct {
|
||||||
|
|
||||||
Gas uint64
|
Gas uint64
|
||||||
value *big.Int
|
value *big.Int
|
||||||
|
|
||||||
Args []byte
|
|
||||||
|
|
||||||
DelegateCall bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContract returns a new contract environment for the execution of EVM.
|
// NewContract returns a new contract environment for the execution of EVM.
|
||||||
func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uint64) *Contract {
|
func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uint64) *Contract {
|
||||||
c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object, Args: nil}
|
c := &Contract{CallerAddress: caller.Address(), caller: caller, self: object}
|
||||||
|
|
||||||
if parent, ok := caller.(*Contract); ok {
|
if parent, ok := caller.(*Contract); ok {
|
||||||
// Reuse JUMPDEST analysis from parent context if available.
|
// Reuse JUMPDEST analysis from parent context if available.
|
||||||
c.jumpdests = parent.jumpdests
|
c.jumpdests = parent.jumpdests
|
||||||
} else {
|
} else {
|
||||||
c.jumpdests = make(destinations)
|
c.jumpdests = make(map[common.Hash]bitvec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gas should be a pointer so it can safely be reduced through the run
|
// Gas should be a pointer so it can safely be reduced through the run
|
||||||
|
@ -84,10 +81,42 @@ func NewContract(caller ContractRef, object ContractRef, value *big.Int, gas uin
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Contract) validJumpdest(dest *big.Int) bool {
|
||||||
|
udest := dest.Uint64()
|
||||||
|
// PC cannot go beyond len(code) and certainly can't be bigger than 63bits.
|
||||||
|
// Don't bother checking for JUMPDEST in that case.
|
||||||
|
if dest.BitLen() >= 63 || udest >= uint64(len(c.Code)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Only JUMPDESTs allowed for destinations
|
||||||
|
if OpCode(c.Code[udest]) != JUMPDEST {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Do we have a contract hash already?
|
||||||
|
if c.CodeHash != (common.Hash{}) {
|
||||||
|
// Does parent context have the analysis?
|
||||||
|
analysis, exist := c.jumpdests[c.CodeHash]
|
||||||
|
if !exist {
|
||||||
|
// Do the analysis and save in parent context
|
||||||
|
// We do not need to store it in c.analysis
|
||||||
|
analysis = codeBitmap(c.Code)
|
||||||
|
c.jumpdests[c.CodeHash] = analysis
|
||||||
|
}
|
||||||
|
return analysis.codeSegment(udest)
|
||||||
|
}
|
||||||
|
// We don't have the code hash, most likely a piece of initcode not already
|
||||||
|
// in state trie. In that case, we do an analysis, and save it locally, so
|
||||||
|
// we don't have to recalculate it for every JUMP instruction in the execution
|
||||||
|
// However, we don't save it within the parent context
|
||||||
|
if c.analysis == nil {
|
||||||
|
c.analysis = codeBitmap(c.Code)
|
||||||
|
}
|
||||||
|
return c.analysis.codeSegment(udest)
|
||||||
|
}
|
||||||
|
|
||||||
// AsDelegate sets the contract to be a delegate call and returns the current
|
// AsDelegate sets the contract to be a delegate call and returns the current
|
||||||
// contract (for chaining calls)
|
// contract (for chaining calls)
|
||||||
func (c *Contract) AsDelegate() *Contract {
|
func (c *Contract) AsDelegate() *Contract {
|
||||||
c.DelegateCall = true
|
|
||||||
// NOTE: caller must, at all times be a contract. It should never happen
|
// NOTE: caller must, at all times be a contract. It should never happen
|
||||||
// that caller is something other than a Contract.
|
// that caller is something other than a Contract.
|
||||||
parent := c.caller.(*Contract)
|
parent := c.caller.(*Contract)
|
||||||
|
@ -138,12 +167,6 @@ func (c *Contract) Value() *big.Int {
|
||||||
return c.value
|
return c.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetCode sets the code to the contract
|
|
||||||
func (c *Contract) SetCode(hash common.Hash, code []byte) {
|
|
||||||
c.Code = code
|
|
||||||
c.CodeHash = hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCallCode sets the code of the contract and address of the backing data
|
// SetCallCode sets the code of the contract and address of the backing data
|
||||||
// object
|
// object
|
||||||
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) {
|
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte) {
|
||||||
|
@ -151,3 +174,11 @@ func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []by
|
||||||
c.CodeHash = hash
|
c.CodeHash = hash
|
||||||
c.CodeAddr = addr
|
c.CodeAddr = addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCodeOptionalHash can be used to provide code, but it's optional to provide hash.
|
||||||
|
// In case hash is not provided, the jumpdest analysis will not be saved to the parent context
|
||||||
|
func (c *Contract) SetCodeOptionalHash(addr *common.Address, codeAndHash *codeAndHash) {
|
||||||
|
c.Code = codeAndHash.code
|
||||||
|
c.CodeHash = codeAndHash.hash
|
||||||
|
c.CodeAddr = addr
|
||||||
|
}
|
||||||
|
|
|
@ -261,6 +261,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
||||||
contract := NewContract(caller, to, value, gas)
|
contract := NewContract(caller, to, value, gas)
|
||||||
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr))
|
||||||
|
|
||||||
|
// Even if the account has no code, we need to continue because it might be a precompile
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// Capture the tracer start/end events in debug mode
|
// Capture the tracer start/end events in debug mode
|
||||||
|
@ -404,8 +405,20 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
|
||||||
return ret, contract.Gas, err
|
return ret, contract.Gas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type codeAndHash struct {
|
||||||
|
code []byte
|
||||||
|
hash common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *codeAndHash) Hash() common.Hash {
|
||||||
|
if c.hash == (common.Hash{}) {
|
||||||
|
c.hash = crypto.Keccak256Hash(c.code)
|
||||||
|
}
|
||||||
|
return c.hash
|
||||||
|
}
|
||||||
|
|
||||||
// create creates a new contract using code as deployment code.
|
// create creates a new contract using code as deployment code.
|
||||||
func (evm *EVM) create(caller ContractRef, code []byte, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
|
func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) {
|
||||||
// Depth check execution. Fail if we're trying to execute above the
|
// Depth check execution. Fail if we're trying to execute above the
|
||||||
// limit.
|
// limit.
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
|
@ -460,15 +473,15 @@ func (evm *EVM) create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||||
// initialise a new contract and set the code that is to be used by the
|
// initialise a new contract and set the code that is to be used by the
|
||||||
// EVM. The contract is a scoped environment for this execution context
|
// EVM. The contract is a scoped environment for this execution context
|
||||||
// only.
|
// only.
|
||||||
contract := NewContract(caller, AccountRef(contractAddr), value, gas)
|
contract := NewContract(caller, AccountRef(address), value, gas)
|
||||||
contract.SetCallCode(&contractAddr, crypto.Keccak256Hash(code), code)
|
contract.SetCodeOptionalHash(&address, codeAndHash)
|
||||||
|
|
||||||
if evm.vmConfig.NoRecursion && evm.depth > 0 {
|
if evm.vmConfig.NoRecursion && evm.depth > 0 {
|
||||||
return nil, contractAddr, gas, nil
|
return nil, contractAddr, gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if evm.vmConfig.Debug && evm.depth == 0 {
|
if evm.vmConfig.Debug && evm.depth == 0 {
|
||||||
evm.vmConfig.Tracer.CaptureStart(caller.Address(), contractAddr, true, code, gas, value)
|
evm.vmConfig.Tracer.CaptureStart(caller.Address(), address, true, codeAndHash.code, gas, value)
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
|
@ -512,7 +525,7 @@ func (evm *EVM) create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||||
// Create creates a new contract using code as deployment code.
|
// Create creates a new contract using code as deployment code.
|
||||||
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||||
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
|
contractAddr = crypto.CreateAddress(caller.Address(), evm.StateDB.GetNonce(caller.Address()))
|
||||||
return evm.create(caller, code, gas, value, contractAddr)
|
return evm.create(caller, &codeAndHash{code: code}, gas, value, contractAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create2 creates a new contract using code as deployment code.
|
// Create2 creates a new contract using code as deployment code.
|
||||||
|
@ -520,8 +533,9 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I
|
||||||
// The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:]
|
// The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:]
|
||||||
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
||||||
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) {
|
||||||
contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), code)
|
codeAndHash := &codeAndHash{code: code}
|
||||||
return evm.create(caller, code, gas, endowment, contractAddr)
|
contractAddr = crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes())
|
||||||
|
return evm.create(caller, codeAndHash, gas, endowment, contractAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChainConfig returns the environment's chain configuration
|
// ChainConfig returns the environment's chain configuration
|
||||||
|
|
|
@ -303,6 +303,17 @@ func gasCreate2(gt params.GasTable, evm *EVM, contract *Contract, stack *Stack,
|
||||||
if gas, overflow = math.SafeAdd(gas, params.Create2Gas); overflow {
|
if gas, overflow = math.SafeAdd(gas, params.Create2Gas); overflow {
|
||||||
return 0, errGasUintOverflow
|
return 0, errGasUintOverflow
|
||||||
}
|
}
|
||||||
|
wordGas, overflow := bigUint64(stack.Back(2))
|
||||||
|
if overflow {
|
||||||
|
return 0, errGasUintOverflow
|
||||||
|
}
|
||||||
|
if wordGas, overflow = math.SafeMul(toWordSize(wordGas), params.Sha3WordGas); overflow {
|
||||||
|
return 0, errGasUintOverflow
|
||||||
|
}
|
||||||
|
if gas, overflow = math.SafeAdd(gas, wordGas); overflow {
|
||||||
|
return 0, errGasUintOverflow
|
||||||
|
}
|
||||||
|
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
var _ = (*structLogMarshaling)(nil)
|
var _ = (*structLogMarshaling)(nil)
|
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON.
|
||||||
func (s StructLog) MarshalJSON() ([]byte, error) {
|
func (s StructLog) MarshalJSON() ([]byte, error) {
|
||||||
type StructLog struct {
|
type StructLog struct {
|
||||||
Pc uint64 `json:"pc"`
|
Pc uint64 `json:"pc"`
|
||||||
|
@ -24,6 +25,7 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
|
||||||
Stack []*math.HexOrDecimal256 `json:"stack"`
|
Stack []*math.HexOrDecimal256 `json:"stack"`
|
||||||
Storage map[common.Hash]common.Hash `json:"-"`
|
Storage map[common.Hash]common.Hash `json:"-"`
|
||||||
Depth int `json:"depth"`
|
Depth int `json:"depth"`
|
||||||
|
RefundCounter uint64 `json:"refund"`
|
||||||
Err error `json:"-"`
|
Err error `json:"-"`
|
||||||
OpName string `json:"opName"`
|
OpName string `json:"opName"`
|
||||||
ErrorString string `json:"error"`
|
ErrorString string `json:"error"`
|
||||||
|
@ -43,12 +45,14 @@ func (s StructLog) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
enc.Storage = s.Storage
|
enc.Storage = s.Storage
|
||||||
enc.Depth = s.Depth
|
enc.Depth = s.Depth
|
||||||
|
enc.RefundCounter = s.RefundCounter
|
||||||
enc.Err = s.Err
|
enc.Err = s.Err
|
||||||
enc.OpName = s.OpName()
|
enc.OpName = s.OpName()
|
||||||
enc.ErrorString = s.ErrorString()
|
enc.ErrorString = s.ErrorString()
|
||||||
return json.Marshal(&enc)
|
return json.Marshal(&enc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
func (s *StructLog) UnmarshalJSON(input []byte) error {
|
func (s *StructLog) UnmarshalJSON(input []byte) error {
|
||||||
type StructLog struct {
|
type StructLog struct {
|
||||||
Pc *uint64 `json:"pc"`
|
Pc *uint64 `json:"pc"`
|
||||||
|
@ -60,6 +64,7 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
|
||||||
Stack []*math.HexOrDecimal256 `json:"stack"`
|
Stack []*math.HexOrDecimal256 `json:"stack"`
|
||||||
Storage map[common.Hash]common.Hash `json:"-"`
|
Storage map[common.Hash]common.Hash `json:"-"`
|
||||||
Depth *int `json:"depth"`
|
Depth *int `json:"depth"`
|
||||||
|
RefundCounter *uint64 `json:"refund"`
|
||||||
Err error `json:"-"`
|
Err error `json:"-"`
|
||||||
}
|
}
|
||||||
var dec StructLog
|
var dec StructLog
|
||||||
|
@ -96,6 +101,9 @@ func (s *StructLog) UnmarshalJSON(input []byte) error {
|
||||||
if dec.Depth != nil {
|
if dec.Depth != nil {
|
||||||
s.Depth = *dec.Depth
|
s.Depth = *dec.Depth
|
||||||
}
|
}
|
||||||
|
if dec.RefundCounter != nil {
|
||||||
|
s.RefundCounter = *dec.RefundCounter
|
||||||
|
}
|
||||||
if dec.Err != nil {
|
if dec.Err != nil {
|
||||||
s.Err = dec.Err
|
s.Err = dec.Err
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"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/crypto/sha3"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -124,10 +124,22 @@ func opSmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory
|
||||||
|
|
||||||
func opExp(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
func opExp(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
||||||
base, exponent := stack.pop(), stack.pop()
|
base, exponent := stack.pop(), stack.pop()
|
||||||
|
// some shortcuts
|
||||||
|
cmpToOne := exponent.Cmp(big1)
|
||||||
|
if cmpToOne < 0 { // Exponent is zero
|
||||||
|
// x ^ 0 == 1
|
||||||
|
stack.push(base.SetUint64(1))
|
||||||
|
} else if base.Sign() == 0 {
|
||||||
|
// 0 ^ y, if y != 0, == 0
|
||||||
|
stack.push(base.SetUint64(0))
|
||||||
|
} else if cmpToOne == 0 { // Exponent is one
|
||||||
|
// x ^ 1 == x
|
||||||
|
stack.push(base)
|
||||||
|
} else {
|
||||||
stack.push(math.Exp(base, exponent))
|
stack.push(math.Exp(base, exponent))
|
||||||
|
interpreter.intPool.put(base)
|
||||||
interpreter.intPool.put(base, exponent)
|
}
|
||||||
|
interpreter.intPool.put(exponent)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,13 +385,20 @@ func opSAR(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *
|
||||||
func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
||||||
offset, size := stack.pop(), stack.pop()
|
offset, size := stack.pop(), stack.pop()
|
||||||
data := memory.Get(offset.Int64(), size.Int64())
|
data := memory.Get(offset.Int64(), size.Int64())
|
||||||
hash := crypto.Keccak256(data)
|
|
||||||
evm := interpreter.evm
|
|
||||||
|
|
||||||
if evm.vmConfig.EnablePreimageRecording {
|
if interpreter.hasher == nil {
|
||||||
evm.StateDB.AddPreimage(common.BytesToHash(hash), data)
|
interpreter.hasher = sha3.NewKeccak256().(keccakState)
|
||||||
|
} else {
|
||||||
|
interpreter.hasher.Reset()
|
||||||
}
|
}
|
||||||
stack.push(interpreter.intPool.get().SetBytes(hash))
|
interpreter.hasher.Write(data)
|
||||||
|
interpreter.hasher.Read(interpreter.hasherBuf[:])
|
||||||
|
|
||||||
|
evm := interpreter.evm
|
||||||
|
if evm.vmConfig.EnablePreimageRecording {
|
||||||
|
evm.StateDB.AddPreimage(interpreter.hasherBuf, data)
|
||||||
|
}
|
||||||
|
stack.push(interpreter.intPool.get().SetBytes(interpreter.hasherBuf[:]))
|
||||||
|
|
||||||
interpreter.intPool.put(offset, size)
|
interpreter.intPool.put(offset, size)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -531,7 +550,12 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract,
|
||||||
// this account should be regarded as a non-existent account and zero should be returned.
|
// this account should be regarded as a non-existent account and zero should be returned.
|
||||||
func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
||||||
slot := stack.peek()
|
slot := stack.peek()
|
||||||
slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(common.BigToAddress(slot)).Bytes())
|
address := common.BigToAddress(slot)
|
||||||
|
if interpreter.evm.StateDB.Empty(address) {
|
||||||
|
slot.SetUint64(0)
|
||||||
|
} else {
|
||||||
|
slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
|
||||||
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -628,7 +652,7 @@ func opSstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memor
|
||||||
|
|
||||||
func opJump(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
func opJump(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
||||||
pos := stack.pop()
|
pos := stack.pop()
|
||||||
if !contract.jumpdests.has(contract.CodeHash, contract.Code, pos) {
|
if !contract.validJumpdest(pos) {
|
||||||
nop := contract.GetOp(pos.Uint64())
|
nop := contract.GetOp(pos.Uint64())
|
||||||
return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
|
return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
|
||||||
}
|
}
|
||||||
|
@ -641,7 +665,7 @@ func opJump(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory
|
||||||
func opJumpi(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
func opJumpi(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
||||||
pos, cond := stack.pop(), stack.pop()
|
pos, cond := stack.pop(), stack.pop()
|
||||||
if cond.Sign() != 0 {
|
if cond.Sign() != 0 {
|
||||||
if !contract.jumpdests.has(contract.CodeHash, contract.Code, pos) {
|
if !contract.validJumpdest(pos) {
|
||||||
nop := contract.GetOp(pos.Uint64())
|
nop := contract.GetOp(pos.Uint64())
|
||||||
return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
|
return nil, fmt.Errorf("invalid jump destination (%v) %v", nop, pos)
|
||||||
}
|
}
|
||||||
|
@ -735,7 +759,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memo
|
||||||
}
|
}
|
||||||
|
|
||||||
func opCall(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
func opCall(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) {
|
||||||
// Pop gas. The actual gas in in interpreter.evm.callGasTemp.
|
// Pop gas. The actual gas in interpreter.evm.callGasTemp.
|
||||||
interpreter.intPool.put(stack.pop())
|
interpreter.intPool.put(stack.pop())
|
||||||
gas := interpreter.evm.callGasTemp
|
gas := interpreter.evm.callGasTemp
|
||||||
// Pop other call parameters.
|
// Pop other call parameters.
|
||||||
|
|
|
@ -17,10 +17,12 @@
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -489,3 +491,99 @@ func BenchmarkOpMstore(bench *testing.B) {
|
||||||
}
|
}
|
||||||
poolOfIntPools.put(evmInterpreter.intPool)
|
poolOfIntPools.put(evmInterpreter.intPool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkOpSHA3(bench *testing.B) {
|
||||||
|
var (
|
||||||
|
env = NewEVM(Context{}, nil, params.TestChainConfig, Config{})
|
||||||
|
stack = newstack()
|
||||||
|
mem = NewMemory()
|
||||||
|
evmInterpreter = NewEVMInterpreter(env, env.vmConfig)
|
||||||
|
)
|
||||||
|
env.interpreter = evmInterpreter
|
||||||
|
evmInterpreter.intPool = poolOfIntPools.get()
|
||||||
|
mem.Resize(32)
|
||||||
|
pc := uint64(0)
|
||||||
|
start := big.NewInt(0)
|
||||||
|
|
||||||
|
bench.ResetTimer()
|
||||||
|
for i := 0; i < bench.N; i++ {
|
||||||
|
stack.pushN(big.NewInt(32), start)
|
||||||
|
opSha3(&pc, evmInterpreter, nil, mem, stack)
|
||||||
|
}
|
||||||
|
poolOfIntPools.put(evmInterpreter.intPool)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreate2Addreses(t *testing.T) {
|
||||||
|
type testcase struct {
|
||||||
|
origin string
|
||||||
|
salt string
|
||||||
|
code string
|
||||||
|
expected string
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range []testcase{
|
||||||
|
{
|
||||||
|
origin: "0x0000000000000000000000000000000000000000",
|
||||||
|
salt: "0x0000000000000000000000000000000000000000",
|
||||||
|
code: "0x00",
|
||||||
|
expected: "0x4d1a2e2bb4f88f0250f26ffff098b0b30b26bf38",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
origin: "0xdeadbeef00000000000000000000000000000000",
|
||||||
|
salt: "0x0000000000000000000000000000000000000000",
|
||||||
|
code: "0x00",
|
||||||
|
expected: "0xB928f69Bb1D91Cd65274e3c79d8986362984fDA3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
origin: "0xdeadbeef00000000000000000000000000000000",
|
||||||
|
salt: "0xfeed000000000000000000000000000000000000",
|
||||||
|
code: "0x00",
|
||||||
|
expected: "0xD04116cDd17beBE565EB2422F2497E06cC1C9833",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
origin: "0x0000000000000000000000000000000000000000",
|
||||||
|
salt: "0x0000000000000000000000000000000000000000",
|
||||||
|
code: "0xdeadbeef",
|
||||||
|
expected: "0x70f2b2914A2a4b783FaEFb75f459A580616Fcb5e",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
origin: "0x00000000000000000000000000000000deadbeef",
|
||||||
|
salt: "0xcafebabe",
|
||||||
|
code: "0xdeadbeef",
|
||||||
|
expected: "0x60f3f640a8508fC6a86d45DF051962668E1e8AC7",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
origin: "0x00000000000000000000000000000000deadbeef",
|
||||||
|
salt: "0xcafebabe",
|
||||||
|
code: "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
|
||||||
|
expected: "0x1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
origin: "0x0000000000000000000000000000000000000000",
|
||||||
|
salt: "0x0000000000000000000000000000000000000000",
|
||||||
|
code: "0x",
|
||||||
|
expected: "0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
|
||||||
|
origin := common.BytesToAddress(common.FromHex(tt.origin))
|
||||||
|
salt := common.BytesToHash(common.FromHex(tt.salt))
|
||||||
|
code := common.FromHex(tt.code)
|
||||||
|
codeHash := crypto.Keccak256(code)
|
||||||
|
address := crypto.CreateAddress2(origin, salt, codeHash)
|
||||||
|
/*
|
||||||
|
stack := newstack()
|
||||||
|
// salt, but we don't need that for this test
|
||||||
|
stack.push(big.NewInt(int64(len(code)))) //size
|
||||||
|
stack.push(big.NewInt(0)) // memstart
|
||||||
|
stack.push(big.NewInt(0)) // value
|
||||||
|
gas, _ := gasCreate2(params.GasTable{}, nil, nil, stack, nil, 0)
|
||||||
|
fmt.Printf("Example %d\n* address `0x%x`\n* salt `0x%x`\n* init_code `0x%x`\n* gas (assuming no mem expansion): `%v`\n* result: `%s`\n\n", i,origin, salt, code, gas, address.String())
|
||||||
|
*/
|
||||||
|
expected := common.BytesToAddress(common.FromHex(tt.expected))
|
||||||
|
if !bytes.Equal(expected.Bytes(), address.Bytes()) {
|
||||||
|
t.Errorf("test %d: expected %s, got %s", i, expected.String(), address.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,8 +18,10 @@ package vm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
@ -68,13 +70,25 @@ type Interpreter interface {
|
||||||
CanRun([]byte) bool
|
CanRun([]byte) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports
|
||||||
|
// Read to get a variable amount of data from the hash state. Read is faster than Sum
|
||||||
|
// because it doesn't copy the internal state, but also modifies the internal state.
|
||||||
|
type keccakState interface {
|
||||||
|
hash.Hash
|
||||||
|
Read([]byte) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
// EVMInterpreter represents an EVM interpreter
|
// EVMInterpreter represents an EVM interpreter
|
||||||
type EVMInterpreter struct {
|
type EVMInterpreter struct {
|
||||||
evm *EVM
|
evm *EVM
|
||||||
cfg Config
|
cfg Config
|
||||||
gasTable params.GasTable
|
gasTable params.GasTable
|
||||||
|
|
||||||
intPool *intPool
|
intPool *intPool
|
||||||
|
|
||||||
|
hasher keccakState // Keccak256 hasher instance shared across opcodes
|
||||||
|
hasherBuf common.Hash // Keccak256 hasher result array shared aross opcodes
|
||||||
|
|
||||||
readOnly bool // Whether to throw on stateful modifications
|
readOnly bool // Whether to throw on stateful modifications
|
||||||
returnData []byte // Last CALL's return data for subsequent reuse
|
returnData []byte // Last CALL's return data for subsequent reuse
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ type StructLog struct {
|
||||||
Stack []*big.Int `json:"stack"`
|
Stack []*big.Int `json:"stack"`
|
||||||
Storage map[common.Hash]common.Hash `json:"-"`
|
Storage map[common.Hash]common.Hash `json:"-"`
|
||||||
Depth int `json:"depth"`
|
Depth int `json:"depth"`
|
||||||
|
RefundCounter uint64 `json:"refund"`
|
||||||
Err error `json:"-"`
|
Err error `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +178,7 @@ func (l *StructLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost ui
|
||||||
storage = l.changedValues[contract.Address()].Copy()
|
storage = l.changedValues[contract.Address()].Copy()
|
||||||
}
|
}
|
||||||
// create a new snaptshot of the EVM.
|
// create a new snaptshot of the EVM.
|
||||||
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, storage, depth, err}
|
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, storage, depth, env.StateDB.GetRefund(), err}
|
||||||
|
|
||||||
l.logs = append(l.logs, log)
|
l.logs = append(l.logs, log)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,9 +42,15 @@ func (d *dummyContractRef) SetBalance(*big.Int) {}
|
||||||
func (d *dummyContractRef) SetNonce(uint64) {}
|
func (d *dummyContractRef) SetNonce(uint64) {}
|
||||||
func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) }
|
func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) }
|
||||||
|
|
||||||
|
type dummyStatedb struct {
|
||||||
|
state.StateDB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*dummyStatedb) GetRefund() uint64 { return 1337 }
|
||||||
|
|
||||||
func TestStoreCapture(t *testing.T) {
|
func TestStoreCapture(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
env = NewEVM(Context{}, nil, nil, params.TestChainConfig, Config{})
|
env = NewEVM(Context{}, &dummyStatedb{}, nil, params.TestChainConfig, Config{})
|
||||||
logger = NewStructLogger(nil)
|
logger = NewStructLogger(nil)
|
||||||
mem = NewMemory()
|
mem = NewMemory()
|
||||||
stack = newstack()
|
stack = newstack()
|
||||||
|
@ -51,9 +58,7 @@ func TestStoreCapture(t *testing.T) {
|
||||||
)
|
)
|
||||||
stack.push(big.NewInt(1))
|
stack.push(big.NewInt(1))
|
||||||
stack.push(big.NewInt(0))
|
stack.push(big.NewInt(0))
|
||||||
|
|
||||||
var index common.Hash
|
var index common.Hash
|
||||||
|
|
||||||
logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, contract, 0, nil)
|
logger.CaptureState(env, 0, SSTORE, 0, 0, mem, stack, contract, 0, nil)
|
||||||
if len(logger.changedValues[contract.Address()]) == 0 {
|
if len(logger.changedValues[contract.Address()]) == 0 {
|
||||||
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.changedValues[contract.Address()]))
|
t.Fatalf("expected exactly 1 changed value on address %x, got %d", contract.Address(), len(logger.changedValues[contract.Address()]))
|
||||||
|
|
|
@ -29,7 +29,7 @@ type Memory struct {
|
||||||
lastGasCost uint64
|
lastGasCost uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMemory returns a new memory memory model.
|
// NewMemory returns a new memory model.
|
||||||
func NewMemory() *Memory {
|
func NewMemory() *Memory {
|
||||||
return &Memory{}
|
return &Memory{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaults(t *testing.T) {
|
func TestDefaults(t *testing.T) {
|
||||||
|
@ -148,3 +149,57 @@ func BenchmarkCall(b *testing.B) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func benchmarkEVM_Create(bench *testing.B, code string) {
|
||||||
|
var (
|
||||||
|
statedb, _ = state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
|
||||||
|
sender = common.BytesToAddress([]byte("sender"))
|
||||||
|
receiver = common.BytesToAddress([]byte("receiver"))
|
||||||
|
)
|
||||||
|
|
||||||
|
statedb.CreateAccount(sender)
|
||||||
|
statedb.SetCode(receiver, common.FromHex(code))
|
||||||
|
runtimeConfig := Config{
|
||||||
|
Origin: sender,
|
||||||
|
State: statedb,
|
||||||
|
GasLimit: 10000000,
|
||||||
|
Difficulty: big.NewInt(0x200000),
|
||||||
|
Time: new(big.Int).SetUint64(0),
|
||||||
|
Coinbase: common.Address{},
|
||||||
|
BlockNumber: new(big.Int).SetUint64(1),
|
||||||
|
ChainConfig: ¶ms.ChainConfig{
|
||||||
|
ChainID: big.NewInt(1),
|
||||||
|
HomesteadBlock: new(big.Int),
|
||||||
|
ByzantiumBlock: new(big.Int),
|
||||||
|
ConstantinopleBlock: new(big.Int),
|
||||||
|
DAOForkBlock: new(big.Int),
|
||||||
|
DAOForkSupport: false,
|
||||||
|
EIP150Block: new(big.Int),
|
||||||
|
EIP155Block: new(big.Int),
|
||||||
|
EIP158Block: new(big.Int),
|
||||||
|
},
|
||||||
|
EVMConfig: vm.Config{},
|
||||||
|
}
|
||||||
|
// Warm up the intpools and stuff
|
||||||
|
bench.ResetTimer()
|
||||||
|
for i := 0; i < bench.N; i++ {
|
||||||
|
Call(receiver, []byte{}, &runtimeConfig)
|
||||||
|
}
|
||||||
|
bench.StopTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEVM_CREATE_500(bench *testing.B) {
|
||||||
|
// initcode size 500K, repeatedly calls CREATE and then modifies the mem contents
|
||||||
|
benchmarkEVM_Create(bench, "5b6207a120600080f0600152600056")
|
||||||
|
}
|
||||||
|
func BenchmarkEVM_CREATE2_500(bench *testing.B) {
|
||||||
|
// initcode size 500K, repeatedly calls CREATE2 and then modifies the mem contents
|
||||||
|
benchmarkEVM_Create(bench, "5b586207a120600080f5600152600056")
|
||||||
|
}
|
||||||
|
func BenchmarkEVM_CREATE_1200(bench *testing.B) {
|
||||||
|
// initcode size 1200K, repeatedly calls CREATE and then modifies the mem contents
|
||||||
|
benchmarkEVM_Create(bench, "5b62124f80600080f0600152600056")
|
||||||
|
}
|
||||||
|
func BenchmarkEVM_CREATE2_1200(bench *testing.B) {
|
||||||
|
// initcode size 1200K, repeatedly calls CREATE2 and then modifies the mem contents
|
||||||
|
benchmarkEVM_Create(bench, "5b5862124f80600080f5600152600056")
|
||||||
|
}
|
||||||
|
|
|
@ -77,9 +77,9 @@ func CreateAddress(b common.Address, nonce uint64) common.Address {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAddress2 creates an ethereum address given the address bytes, initial
|
// CreateAddress2 creates an ethereum address given the address bytes, initial
|
||||||
// contract code and a salt.
|
// contract code hash and a salt.
|
||||||
func CreateAddress2(b common.Address, salt [32]byte, code []byte) common.Address {
|
func CreateAddress2(b common.Address, salt [32]byte, inithash []byte) common.Address {
|
||||||
return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], Keccak256(code))[12:])
|
return common.BytesToAddress(Keccak256([]byte{0xff}, b.Bytes(), salt[:], inithash)[12:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToECDSA creates a private key with the given D value.
|
// ToECDSA creates a private key with the given D value.
|
||||||
|
|
|
@ -54,7 +54,7 @@ static void secp256k1_num_mul(secp256k1_num *r, const secp256k1_num *a, const se
|
||||||
even if r was negative. */
|
even if r was negative. */
|
||||||
static void secp256k1_num_mod(secp256k1_num *r, const secp256k1_num *m);
|
static void secp256k1_num_mod(secp256k1_num *r, const secp256k1_num *m);
|
||||||
|
|
||||||
/** Right-shift the passed number by bits bits. */
|
/** Right-shift the passed number by bits. */
|
||||||
static void secp256k1_num_shift(secp256k1_num *r, int bits);
|
static void secp256k1_num_shift(secp256k1_num *r, int bits);
|
||||||
|
|
||||||
/** Check whether a number is zero. */
|
/** Check whether a number is zero. */
|
||||||
|
|
|
@ -94,6 +94,15 @@ func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 {
|
||||||
return hexutil.Uint64(api.e.Miner().HashRate())
|
return hexutil.Uint64(api.e.Miner().HashRate())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config.
|
||||||
|
func (api *PublicEthereumAPI) ChainId() hexutil.Uint64 {
|
||||||
|
chainID := new(big.Int)
|
||||||
|
if config := api.e.chainConfig; config.IsEIP155(api.e.blockchain.CurrentBlock().Number()) {
|
||||||
|
chainID = config.ChainID
|
||||||
|
}
|
||||||
|
return (hexutil.Uint64)(chainID.Uint64())
|
||||||
|
}
|
||||||
|
|
||||||
// PublicMinerAPI provides an API to control the miner.
|
// PublicMinerAPI provides an API to control the miner.
|
||||||
// It offers only methods that operate on data that pose no security risk when it is publicly accessible.
|
// It offers only methods that operate on data that pose no security risk when it is publicly accessible.
|
||||||
type PublicMinerAPI struct {
|
type PublicMinerAPI struct {
|
||||||
|
|
|
@ -25,7 +25,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/math"
|
"github.com/ethereum/go-ethereum/common/math"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
@ -120,18 +119,11 @@ func (b *EthAPIBackend) GetBlock(ctx context.Context, hash common.Hash) (*types.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
func (b *EthAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
||||||
if number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash); number != nil {
|
return b.eth.blockchain.GetReceiptsByHash(hash), nil
|
||||||
return rawdb.ReadReceipts(b.eth.chainDb, hash, *number), nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
|
func (b *EthAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
|
||||||
number := rawdb.ReadHeaderNumber(b.eth.chainDb, hash)
|
receipts := b.eth.blockchain.GetReceiptsByHash(hash)
|
||||||
if number == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
receipts := rawdb.ReadReceipts(b.eth.chainDb, hash, *number)
|
|
||||||
if receipts == nil {
|
if receipts == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|