From f7d5ce4985258d78b1d9913f75cbd81bfa7fd29d Mon Sep 17 00:00:00 2001 From: zelig Date: Mon, 14 Dec 2015 21:34:17 +0000 Subject: [PATCH] SWORM - swarm poc 0.2 homestead RC1 synced up to go-ethereum 1.5.0-unstable develop branch major features: * api overhaul due to rpc v2 and node service stack interface changes * blockchain/ethereum contract interaction rewritten using abi/abigen (chequebook, ens) * swarm - cluster control CLI - migration and revamp of prehistoric eth-utils repo * poor man's end to end testing: scripted scenarios in swarm/test using swarm CLI * http proxy now handles 3 url schemes for 1) ens-enabled [bzz], 2) immutable [bzzi] and 3) raw manifest [bzzr] resolution * fixes issues with remote address setting, forwarding and syncing * new control flags to switch swap and sync on and off * placeholder basic implementation Ethereum Name Service * improved logging - now debug level is coherent regression: * uri based versioning support is dropped temporarily since state tree pruning does not guarantee historical record * registrar related functionality temporarily restricted - current ENS provides basic free and unrestricted Register/Resolve accounts/abi: * bind: repeated attempt deployment of contracts, validation against known code, transactor creation from private keys * accountmanager: getUnlocked snatch private key when unlocked cmd: * unlockAccount moved to utils/cmd and exported * getPassPhrase moved to utils/input and exported * accountcmds: reflect the change * js: GlobalRegistrar is dropped (ens) flags: * chequebook, bzzaccount, bzzport, bzzconfig, bzznoswap, bzznosync chequebook: * move from common/ to swarm/services * abigen-ised * specifies its own API (removed chequebook api from swarm/api) kademlia: * move from common to swarm/network/kademlia * address abstracted out to separate file + tests dns/ens/registrar: * moved from swarm/api to swarm/services/ens * implementation is basic placeholder before ENS is implemented * temporary rpc api via ens namespace * the old common/registrar is removed (also from eth/backend apis) swap: * the abstract swap module moved from common to swarm/services * now embedded in the swarm and chequebook specific setup (this will change) * safer chequebook deployment using abigen helpers * backend not field of swap params eth: * public accessor for GPO, needed to construct a PublicBlockChainAPI * extends ContractBackend in eth/bind.go with BalanceAt, GetTxReceipt and CodeAt API calls internal/web3ext * add js bindings for bzz, chequebook rpc apis swarm/api: * refactored api into smaller modules filesystem/storage/testapi * ethereum backend (needed for dns, swap, etc) moved to abi/bind * TODO: further refactor due to #2040 * swarm/api/http: now supports the 3 uri schemes * examples/album updated swarm/cmd: * migrate old eth-utils and modify into a cluster control CLI * bzzup now allows non-local gateway, endpoint specified as second argument swarm/network: * forwarder improved log messages, fixup condition on whether syncer is nil * hive extended with controls for testing support block read/write, swap/sync enabled/disabled * hive keepAlive launches with alarm in case no discover and no kaddb * fix IP address formatting issue [::1] -> became ::1 which refused to dial, now use discover.NewNode#String * integrate functionality for enabling/disabling sync and swap * allow nil sync state - improve syncer interface in protocol * fix SData slice out of bounds bug * fix disconnects by not replacing a node when bucket is full swarm/test * poor man's testing framework. scripts invoking swarm/cmd/swarm * added tests for basic scenarios connections, swap, sync swarm: * rewrite api using rpc v2 * blockchain comms via abi/abigen + eth.ContractBackend * integrate new flags --- accounts/abi/bind/ext.go | 109 ++++ accounts/account_manager.go | 4 +- cmd/geth/accountcmd.go | 97 +-- cmd/geth/js.go | 3 - cmd/geth/js_bzz_test.go | 83 --- cmd/geth/js_test.go | 57 +- cmd/geth/main.go | 94 +-- cmd/utils/cmd.go | 55 ++ cmd/utils/flags.go | 115 ++-- cmd/utils/input.go | 30 + common/chequebook/contract.go | 63 -- common/registrar/contracts.go | 163 ----- common/registrar/ethreg/api.go | 279 --------- common/registrar/registrar.go | 436 -------------- common/registrar/registrar_test.go | 158 ----- crypto/crypto.go | 8 +- crypto/crypto_test.go | 1 + crypto/secp256k1/curve_test.go | 33 - eth/backend.go | 9 +- eth/bind.go | 21 + eth/downloader/modes.go | 22 - internal/web3ext/web3ext.go | 178 +++++- rpc/api/bzz.go | 278 --------- rpc/api/bzz_args.go | 322 ---------- rpc/api/bzz_js.go | 95 --- rpc/api/utils.go | 251 -------- swarm/api/api.go | 487 ++++----------- swarm/api/api_test.go | 241 ++------ swarm/api/config_test.go | 1 + swarm/api/ethereum.go | 320 ---------- swarm/api/filesystem.go | 257 ++++++++ swarm/api/filesystem_test.go | 176 ++++++ swarm/api/http/roundtripper.go | 53 ++ swarm/api/{ => http}/roundtripper_test.go | 8 +- swarm/api/{http.go => http/server.go} | 59 +- swarm/api/manifest.go | 9 +- swarm/api/roundtripper.go | 22 - swarm/api/storage.go | 48 ++ swarm/api/storage_test.go | 33 + swarm/api/testapi.go | 30 + swarm/cmd/bzzup.sh | 17 +- swarm/cmd/swarm/gethup.sh | 149 +++++ swarm/cmd/swarm/swarm.sh | 316 ++++++++++ swarm/cmd/swarm/test.sh | 28 + swarm/examples/album/file-manager.js | 135 +++++ swarm/examples/album/images/favicon.ico | Bin 0 -> 305 bytes swarm/examples/album/index.css | 4 + swarm/examples/album/index.html | 30 +- swarm/examples/album/index.js | 105 +--- swarm/examples/album/jquery.js | 4 + swarm/examples/bzzhandler.html | 13 +- swarm/network/depo.go | 9 +- swarm/network/forwarding.go | 37 +- swarm/network/hive.go | 84 ++- {common => swarm/network}/kademlia/address.go | 0 .../network}/kademlia/address_test.go | 0 {common => swarm/network}/kademlia/kaddb.go | 7 +- .../network}/kademlia/kademlia.go | 143 ++--- .../network}/kademlia/kademlia_test.go | 0 swarm/network/messages.go | 19 +- swarm/network/protocol.go | 125 ++-- swarm/network/syncdb.go | 21 +- swarm/network/syncer.go | 48 +- swarm/services/chequebook/api.go | 52 ++ .../services}/chequebook/cheque.go | 308 ++++------ .../services}/chequebook/cheque_test.go | 347 ++++++----- .../chequebook/contract/chequebook.go | 542 +++++++++++++++++ .../chequebook/contract}/chequebook.sol | 10 +- .../services/chequebook/contract/contract.go | 5 + swarm/services/ens/contract/ens.go | 566 ++++++++++++++++++ swarm/services/ens/contract/ens.sol | 19 + swarm/services/ens/ens.go | 84 +++ swarm/services/ens/ens_test.go | 56 ++ swarm/services/swap/swap.go | 132 ++-- {common => swarm/services/swap}/swap/swap.go | 8 +- .../services/swap}/swap/swap_test.go | 0 swarm/services/swear/swear.sol | 4 +- swarm/storage/chunker.go | 2 +- swarm/storage/database.go | 13 - swarm/swarm.go | 159 +++-- swarm/test/connections/00.sh | 33 + swarm/test/swap/00.sh | 29 + swarm/test/swap/01.sh | 43 ++ swarm/test/syncing/00.sh | 110 ++-- swarm/test/syncing/01.sh | 24 + swarm/test/syncing/02.sh | 30 + 86 files changed, 4266 insertions(+), 4312 deletions(-) create mode 100644 accounts/abi/bind/ext.go delete mode 100644 cmd/geth/js_bzz_test.go delete mode 100644 common/chequebook/contract.go delete mode 100644 common/registrar/contracts.go delete mode 100644 common/registrar/ethreg/api.go delete mode 100644 common/registrar/registrar.go delete mode 100644 common/registrar/registrar_test.go delete mode 100644 rpc/api/bzz.go delete mode 100644 rpc/api/bzz_args.go delete mode 100644 rpc/api/bzz_js.go delete mode 100644 rpc/api/utils.go delete mode 100644 swarm/api/ethereum.go create mode 100644 swarm/api/filesystem.go create mode 100644 swarm/api/filesystem_test.go create mode 100644 swarm/api/http/roundtripper.go rename swarm/api/{ => http}/roundtripper_test.go (86%) rename swarm/api/{http.go => http/server.go} (80%) delete mode 100644 swarm/api/roundtripper.go create mode 100644 swarm/api/storage.go create mode 100644 swarm/api/storage_test.go create mode 100644 swarm/api/testapi.go create mode 100644 swarm/cmd/swarm/gethup.sh create mode 100644 swarm/cmd/swarm/swarm.sh create mode 100644 swarm/cmd/swarm/test.sh create mode 100644 swarm/examples/album/file-manager.js create mode 100644 swarm/examples/album/images/favicon.ico create mode 100644 swarm/examples/album/jquery.js rename {common => swarm/network}/kademlia/address.go (100%) rename {common => swarm/network}/kademlia/address_test.go (100%) rename {common => swarm/network}/kademlia/kaddb.go (99%) rename {common => swarm/network}/kademlia/kademlia.go (77%) rename {common => swarm/network}/kademlia/kademlia_test.go (100%) create mode 100644 swarm/services/chequebook/api.go rename {common => swarm/services}/chequebook/cheque.go (66%) rename {common => swarm/services}/chequebook/cheque_test.go (52%) create mode 100644 swarm/services/chequebook/contract/chequebook.go rename {common/chequebook => swarm/services/chequebook/contract}/chequebook.sol (85%) create mode 100644 swarm/services/chequebook/contract/contract.go create mode 100644 swarm/services/ens/contract/ens.go create mode 100644 swarm/services/ens/contract/ens.sol create mode 100644 swarm/services/ens/ens.go create mode 100644 swarm/services/ens/ens_test.go rename {common => swarm/services/swap}/swap/swap.go (96%) rename {common => swarm/services/swap}/swap/swap_test.go (100%) create mode 100644 swarm/test/connections/00.sh create mode 100644 swarm/test/swap/00.sh create mode 100644 swarm/test/swap/01.sh create mode 100644 swarm/test/syncing/01.sh create mode 100644 swarm/test/syncing/02.sh diff --git a/accounts/abi/bind/ext.go b/accounts/abi/bind/ext.go new file mode 100644 index 0000000000..3b9ffb3edd --- /dev/null +++ b/accounts/abi/bind/ext.go @@ -0,0 +1,109 @@ +package bind + +import ( + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +type DeployOptions struct { + ReceiptQueryInterval time.Duration + DeployRetryInterval time.Duration + ConfirmationInterval time.Duration + MaxReceiptQueryAttempts int + MaxDeployAttempts int +} + +func DefaultDeployOptions() *DeployOptions { + return &DeployOptions{ + ReceiptQueryInterval: 10000000000, // 10 sec + DeployRetryInterval: 5000000000, // 5 sec + ConfirmationInterval: 60000000000, // 60 sec + MaxReceiptQueryAttempts: 5, + MaxDeployAttempts: 100, + } +} + +// implemented by eth.APIBackend +type Backend interface { + GetTxReceipt(txhash common.Hash) *types.Receipt + CodeAt(address common.Address) string + BalanceAt(address common.Address) *big.Int + ContractBackend +} + +func Deploy(deployF func(*TransactOpts, ContractBackend) (*types.Transaction, error), contractCode string, deployTransactor *TransactOpts, opt *DeployOptions, backend Backend) (contractAddr common.Address, err error) { + + deployRetryTimer := time.NewTimer(0).C + var receiptQueryTimer <-chan time.Time + var deployRetries, receiptQueries int + var txhash common.Hash + +DEPLOY: + for { + select { + case <-deployRetryTimer: + deployRetries++ + if deployRetries == opt.MaxDeployAttempts { + return common.Address{}, fmt.Errorf("deployment failed...giving up after %v attempts", opt.MaxDeployAttempts) + } + tx, err := deployF(deployTransactor, backend) + if err != nil { + glog.V(logger.Warn).Infof("deployment failed: %v (attempt %v)", err, deployRetries) + deployRetryTimer = time.NewTimer(opt.DeployRetryInterval).C + continue DEPLOY + } + + txhash = tx.Hash() + deployRetryTimer = nil + receiptQueryTimer = time.NewTimer(0).C + + case <-receiptQueryTimer: + receipt := backend.GetTxReceipt(txhash) + receiptQueries++ + if receipt == nil { + if receiptQueries == opt.MaxReceiptQueryAttempts { + glog.V(logger.Warn).Infof("attempt %s deployment failed. Given up after %v attempts", opt.MaxReceiptQueryAttempts) + + deployRetryTimer = time.NewTimer(opt.DeployRetryInterval).C + receiptQueryTimer = nil + continue DEPLOY + + } + glog.V(logger.Detail).Infof("new checkbook contract (txhash: %v) not yet mined... checking in %v", txhash.Hex(), opt.ReceiptQueryInterval) + receiptQueryTimer = time.NewTimer(opt.ReceiptQueryInterval).C + continue DEPLOY + } + + contractAddr = receipt.ContractAddress + glog.V(logger.Detail).Infof("new chequebook contract mined at %v (owner: %v)", contractAddr.Hex(), deployTransactor.From.Hex()) + <-time.NewTimer(opt.ConfirmationInterval).C + err = Validate(contractAddr, contractCode, backend) + if err != nil { + glog.V(logger.Warn).Infof("invalid contract at %v after %v: %v", contractAddr.Hex(), opt.ConfirmationInterval, err) + deployRetryTimer = time.NewTimer(opt.DeployRetryInterval).C + receiptQueryTimer = nil + continue DEPLOY + } + break DEPLOY + + } // select + } // for + return contractAddr, nil +} + +func Validate(contractAddr common.Address, expCode string, backend Backend) (err error) { + if (contractAddr == common.Address{}) { + return fmt.Errorf("zero address") + } + code := backend.CodeAt(contractAddr) + if len(expCode) > 0 && code != expCode { + return fmt.Errorf("incorrect code %v:\n%v\n%v", contractAddr.Hex(), code, expCode) + } + return nil +} diff --git a/accounts/account_manager.go b/accounts/account_manager.go index 0b414666ec..57ab89fdff 100644 --- a/accounts/account_manager.go +++ b/accounts/account_manager.go @@ -148,8 +148,8 @@ func (am *Manager) Sign(addr common.Address, hash []byte) (signature []byte, err } func (am *Manager) GetUnlocked(addr common.Address) (prvkey *ecdsa.PrivateKey, err error) { - am.mutex.RLock() - defer am.mutex.RUnlock() + am.mu.RLock() + defer am.mu.RUnlock() unlockedKey, found := am.unlocked[addr] if !found { return nil, ErrLocked diff --git a/cmd/geth/accountcmd.go b/cmd/geth/accountcmd.go index bf754c72f1..d4f26e52c2 100644 --- a/cmd/geth/accountcmd.go +++ b/cmd/geth/accountcmd.go @@ -21,11 +21,8 @@ import ( "io/ioutil" "github.com/codegangsta/cli" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" ) var ( @@ -173,94 +170,10 @@ func accountList(ctx *cli.Context) { } } -// tries unlocking the specified account a few times. -func unlockAccount(ctx *cli.Context, accman *accounts.Manager, address string, i int, passwords []string) (accounts.Account, string) { - account, err := utils.MakeAddress(accman, address) - if err != nil { - utils.Fatalf("Could not list accounts: %v", err) - } - for trials := 0; trials < 3; trials++ { - prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) - password := getPassPhrase(prompt, false, i, passwords) - err = accman.Unlock(account, password) - if err == nil { - glog.V(logger.Info).Infof("Unlocked account %x", account.Address) - return account, password - } - if err, ok := err.(*accounts.AmbiguousAddrError); ok { - glog.V(logger.Info).Infof("Unlocked account %x", account.Address) - return ambiguousAddrRecovery(accman, err, password), password - } - if err != accounts.ErrDecrypt { - // No need to prompt again if the error is not decryption-related. - break - } - } - // All trials expended to unlock account, bail out - utils.Fatalf("Failed to unlock account %s (%v)", address, err) - return accounts.Account{}, "" -} - -// getPassPhrase retrieves the passwor associated with an account, either fetched -// from a list of preloaded passphrases, or requested interactively from the user. -func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string { - // If a list of passwords was supplied, retrieve from them - if len(passwords) > 0 { - if i < len(passwords) { - return passwords[i] - } - return passwords[len(passwords)-1] - } - // Otherwise prompt the user for the password - if prompt != "" { - fmt.Println(prompt) - } - password, err := utils.Stdin.PasswordPrompt("Passphrase: ") - if err != nil { - utils.Fatalf("Failed to read passphrase: %v", err) - } - if confirmation { - confirm, err := utils.Stdin.PasswordPrompt("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 -} - -func ambiguousAddrRecovery(am *accounts.Manager, err *accounts.AmbiguousAddrError, auth string) accounts.Account { - fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) - for _, a := range err.Matches { - fmt.Println(" ", a.File) - } - fmt.Println("Testing your passphrase against all of them...") - var match *accounts.Account - for _, a := range err.Matches { - if err := am.Unlock(a, auth); err == nil { - match = &a - break - } - } - if match == nil { - utils.Fatalf("None of the listed files could be unlocked.") - } - fmt.Printf("Your passphrase unlocked %s\n", match.File) - fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") - for _, a := range err.Matches { - if a != *match { - fmt.Println(" ", a.File) - } - } - return *match -} - // accountCreate creates a new account into the keystore defined by the CLI flags. func accountCreate(ctx *cli.Context) { accman := utils.MakeAccountManager(ctx) - password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) + password := utils.GetPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) account, err := accman.NewAccount(password) if err != nil { @@ -277,8 +190,8 @@ func accountUpdate(ctx *cli.Context) { } accman := utils.MakeAccountManager(ctx) - account, oldPassword := unlockAccount(ctx, accman, ctx.Args().First(), 0, nil) - newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) + account, oldPassword := utils.UnlockAccount(accman, ctx.Args().First(), 0, nil) + newPassword := utils.GetPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) if err := accman.Update(account, oldPassword, newPassword); err != nil { utils.Fatalf("Could not update the account: %v", err) } @@ -295,7 +208,7 @@ func importWallet(ctx *cli.Context) { } accman := utils.MakeAccountManager(ctx) - passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx)) + passphrase := utils.GetPassPhrase("", false, 0, utils.MakePasswordList(ctx)) acct, err := accman.ImportPreSaleKey(keyJson, passphrase) if err != nil { @@ -314,7 +227,7 @@ func accountImport(ctx *cli.Context) { utils.Fatalf("keyfile must be given as argument") } accman := utils.MakeAccountManager(ctx) - passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) + passphrase := utils.GetPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) acct, err := accman.ImportECDSA(key, passphrase) if err != nil { utils.Fatalf("Could not create the account: %v", err) diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 2b64303b2b..178fc76c94 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -30,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/registrar" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/internal/web3ext" re "github.com/ethereum/go-ethereum/jsre" @@ -217,8 +216,6 @@ func (js *jsre) apiBindings() error { utils.Fatalf("Error setting namespaces: %v", err) } - js.re.Run(`var GlobalRegistrar = eth.contract(` + registrar.GlobalRegistrarAbi + `); registrar = GlobalRegistrar.at("` + registrar.GlobalRegistrarAddr + `");`) - // overrule some of the methods that require password as input and ask for it interactively p, err := js.re.Get("personal") if err != nil { diff --git a/cmd/geth/js_bzz_test.go b/cmd/geth/js_bzz_test.go deleted file mode 100644 index 2d8b70efb9..0000000000 --- a/cmd/geth/js_bzz_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package main - -import ( - "io/ioutil" - "os" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/swarm" - "github.com/ethereum/go-ethereum/swarm/api" -) - -var port = 8500 - -func bzzREPL(t *testing.T, configf func(*api.Config)) (string, string, *testjethre, *node.Node) { - prvKey, err := crypto.GenerateKey() - if err != nil { - t.Fatal("unable to generate key") - } - bzztmp, err := ioutil.TempDir("", "bzz-js-test") - config, err := api.NewConfig(bzztmp, common.Address{}, prvKey) - if err != nil { - t.Fatal("unable to configure swarm") - } - if configf != nil { - configf(config) - } - tmp, repl, stack := testREPL(t, func(n *node.Node) { - if err := n.Register(func(ctx *node.ServiceContext) (node.Service, error) { - return swarm.NewSwarm(ctx, config, false) - }); err != nil { - t.Fatalf("Failed to register the Swarm service: %v", err) - } - }) - return bzztmp, tmp, repl, stack -} - -func withREPL(t *testing.T, cf func(*api.Config), f func(repl *testjethre)) { - bzztmp, tmp, repl, stack := bzzREPL(t, cf) - defer stack.Stop() - defer os.RemoveAll(tmp) - defer os.RemoveAll(bzztmp) - f(repl) -} - -func TestBzzPutGet(t *testing.T) { - withREPL(t, - func(c *api.Config) { - c.Port = "" - }, func(repl *testjethre) { - if checkEvalJSON(t, repl, `hash = bzz.put("console.log(\"hello from console\")", "application/javascript")`, `"97f1b7c7ea12468fd37c262383b9aa862d0cfbc4fc7218652374679fc5cf40cd"`) != nil { - return - } - want := `{"content":"console.log(\"hello from console\")","contentType":"application/javascript","size":"33","status":"0"}` - if checkEvalJSON(t, repl, `bzz.get(hash)`, want) != nil { - return - } - }) -} - -// the server can be initialized only once per test session ! -// until we implement a stoppable http server -// further http tests will need to make sure the correct server is running -func TestHTTP(t *testing.T) { - withREPL(t, nil, func(repl *testjethre) { - if checkEvalJSON(t, repl, `hash = bzz.put("f42 = function() { return 42 }", "application/javascript")`, `"e6847876f00102441f850b2d438a06d10e3bf24e6a0a76d47b073a86c3c2f9ac"`) != nil { - return - } - if checkEvalJSON(t, repl, `admin.httpGet("bzz://"+hash)`, `"f42 = function() { return 42 }"`) != nil { - return - } - - // if checkEvalJSON(t, repl, `http.loadScript("bzz://"+hash)`, `true`) != nil { - // return - // } - - // if checkEvalJSON(t, repl, `f42()`, `42`) != nil { - // return - // } - }) -} diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go index 7a240d38f3..ddfe0d4000 100644 --- a/cmd/geth/js_test.go +++ b/cmd/geth/js_test.go @@ -29,7 +29,6 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/common/httpclient" @@ -43,18 +42,17 @@ import ( const ( testSolcPath = "" solcVersion = "0.9.23" - - testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674" - testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" - testBalance = "10000000000000000000" + testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182" + testBalance = "10000000000000000000" // of empty string testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" ) var ( - versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`)) - testNodeKey = crypto.ToECDSA(common.Hex2Bytes("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f")) - testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}` + versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`)) + testNodeKey, _ = crypto.HexToECDSA("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f") + testAccount, _ = crypto.HexToECDSA("e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674") + testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}` ) type testjethre struct { @@ -63,17 +61,6 @@ type testjethre struct { client *httpclient.HTTPClient } -func (self *testjethre) UnlockAccount(acc []byte) bool { - var ethereum *eth.Ethereum - self.stack.Service(ðereum) - - err := ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "") - if err != nil { - panic("unable to unlock") - } - return true -} - // Temporary disabled while natspec hasn't been migrated //func (self *testjethre) ConfirmTransaction(tx string) bool { // var ethereum *eth.Ethereum @@ -95,18 +82,19 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod t.Fatal(err) } // Create a networkless protocol stack - stack, err := node.New(&node.Config{PrivateKey: testNodeKey, Name: "test", NoDiscovery: true}) + stack, err := node.New(&node.Config{DataDir: tmp, PrivateKey: testNodeKey, Name: "test", NoDiscovery: true}) if err != nil { t.Fatalf("failed to create node: %v", err) } // Initialize and register the Ethereum protocol - keystore := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore")) - accman := accounts.NewManager(keystore) - + accman := accounts.NewPlaintextManager(filepath.Join(tmp, "keystore")) db, _ := ethdb.NewMemDatabase() - core.WriteGenesisBlockForTesting(db, core.GenesisAccount{common.HexToAddress(testAddress), common.String2Big(testBalance)}) - + core.WriteGenesisBlockForTesting(db, core.GenesisAccount{ + Address: common.HexToAddress(testAddress), + Balance: common.String2Big(testBalance), + }) ethConf := ð.Config{ + ChainConfig: &core.ChainConfig{HomesteadBlock: new(big.Int)}, TestGenesisState: db, AccountManager: accman, DocRoot: "/", @@ -122,15 +110,11 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod t.Fatalf("failed to register ethereum protocol: %v", err) } // Initialize all the keys for testing - keyb, err := crypto.HexToECDSA(testKey) + a, err := accman.ImportECDSA(testAccount, "") if err != nil { t.Fatal(err) } - key := crypto.NewKeyFromECDSA(keyb) - if err := keystore.StoreKey(key, ""); err != nil { - t.Fatal(err) - } - if err := accman.Unlock(key.Address, ""); err != nil { + if err := accman.Unlock(a, ""); err != nil { t.Fatal(err) } // Start the node and assemble the REPL tester @@ -141,8 +125,10 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod stack.Service(ðereum) assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext") - //client := comms.NewInProcClient(codec.JSON) - client := utils.NewInProcRPCClient(stack) + client, err := stack.Attach() + if err != nil { + t.Fatalf("failed to attach to node: %v", err) + } tf := &testjethre{client: ethereum.HTTPClient()} repl := newJSRE(stack, assetPath, "", client, false) tf.jsre = repl @@ -152,9 +138,6 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod func TestNodeInfo(t *testing.T) { t.Skip("broken after p2p update") tmp, repl, ethereum := testJEthRE(t) - if err := ethereum.Start(); err != nil { - t.Fatalf("error starting ethereum: %v", err) - } defer ethereum.Stop() defer os.RemoveAll(tmp) @@ -398,7 +381,7 @@ multiply7 = Multiply7.at(contractaddress); if sol != nil && solcVersion != sol.Version() { modContractInfo := versionRE.ReplaceAll(contractInfo, []byte(`"compilerVersion":"`+sol.Version()+`"`)) fmt.Printf("modified contractinfo:\n%s\n", modContractInfo) - contentHash = `"` + common.ToHex(crypto.Sha3([]byte(modContractInfo))) + `"` + contentHash = `"` + common.ToHex(crypto.Keccak256([]byte(modContractInfo))) + `"` } if checkEvalJSON(t, repl, `filename = "/tmp/info.json"`, `"/tmp/info.json"`) != nil { return diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 45684285e6..4dabfe5076 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -4,7 +4,7 @@ // 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. +// (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 @@ -216,9 +216,6 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.ExecFlag, utils.PreLoadJSFlag, utils.WhisperEnabledFlag, - utils.SwarmConfigPathFlag, - utils.SwarmAccountAddrFlag, - utils.ChequebookAddrFlag, utils.DevModeFlag, utils.TestNetFlag, utils.VMForceJitFlag, @@ -236,6 +233,13 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.GpobaseStepUpFlag, utils.GpobaseCorrectionFactorFlag, utils.ExtraDataFlag, + // on top of develop + utils.SwarmConfigPathFlag, + utils.SwarmSwapDisabled, + utils.SwarmSyncDisabled, + utils.SwarmPortFlag, + utils.SwarmAccountAddrFlag, + utils.ChequebookAddrFlag, } app.Flags = append(app.Flags, debug.Flags...) @@ -440,16 +444,6 @@ func startNode(ctx *cli.Context, stack *node.Node) { if err := stack.Service(ðereum); err != nil { utils.Fatalf("ethereum service not running: %v", err) } - accman := ethereum.AccountManager() - passwords := utils.MakePasswordList(ctx) - - accounts := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",") - for i, account := range accounts { - if trimmed := strings.TrimSpace(account); trimmed != "" { - unlockAccount(ctx, accman, trimmed, i, passwords) - } - } - // Start auxiliary services if enabled if ctx.GlobalBool(utils.MiningEnabledFlag.Name) { if err := ethereum.StartMining(ctx.GlobalInt(utils.MinerThreadsFlag.Name), ctx.GlobalString(utils.MiningGPUFlag.Name)); err != nil { utils.Fatalf("Failed to start mining: %v", err) @@ -457,78 +451,6 @@ func startNode(ctx *cli.Context, stack *node.Node) { } } -func accountList(ctx *cli.Context) { - accman := utils.MakeAccountManager(ctx) - accts, err := accman.Accounts() - if err != nil { - utils.Fatalf("Could not list accounts: %v", err) - } - for i, acct := range accts { - fmt.Printf("Account #%d: %x\n", i, acct) - } -} - -// accountCreate creates a new account into the keystore defined by the CLI flags. -func accountCreate(ctx *cli.Context) { - accman := utils.MakeAccountManager(ctx) - password := utils.GetPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) - - account, err := accman.NewAccount(password) - if err != nil { - utils.Fatalf("Failed to create account: %v", err) - } - fmt.Printf("Address: %x\n", account) -} - -// accountUpdate transitions an account from a previous format to the current -// one, also providing the possibility to change the pass-phrase. -func accountUpdate(ctx *cli.Context) { - if len(ctx.Args()) == 0 { - utils.Fatalf("No accounts specified to update") - } - accman := utils.MakeAccountManager(ctx) - - account, oldPassword := utils.UnlockAccount(ctx, accman, ctx.Args().First(), 0, nil) - newPassword := utils.GetPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil) - if err := accman.Update(account, oldPassword, newPassword); err != nil { - utils.Fatalf("Could not update the account: %v", err) - } -} - -func importWallet(ctx *cli.Context) { - keyfile := ctx.Args().First() - if len(keyfile) == 0 { - utils.Fatalf("keyfile must be given as argument") - } - keyJson, err := ioutil.ReadFile(keyfile) - if err != nil { - utils.Fatalf("Could not read wallet file: %v", err) - } - - accman := utils.MakeAccountManager(ctx) - passphrase := utils.GetPassPhrase("", false, 0, utils.MakePasswordList(ctx)) - - acct, err := accman.ImportPreSaleKey(keyJson, passphrase) - if err != nil { - utils.Fatalf("Could not create the account: %v", err) - } - fmt.Printf("Address: %x\n", acct) -} - -func accountImport(ctx *cli.Context) { - keyfile := ctx.Args().First() - if len(keyfile) == 0 { - utils.Fatalf("keyfile must be given as argument") - } - accman := utils.MakeAccountManager(ctx) - passphrase := utils.GetPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx)) - acct, err := accman.Import(keyfile, passphrase) - if err != nil { - utils.Fatalf("Could not create the account: %v", err) - } - fmt.Printf("Address: %x\n", acct) -} - func makedag(ctx *cli.Context) { args := ctx.Args() wrongArgs := func() { diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index 9e2b14f567..7cfad99c67 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -24,6 +24,7 @@ import ( "os/signal" "regexp" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" @@ -209,3 +210,57 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las glog.Infoln("Exported blockchain to", fn) return nil } + +// tries unlocking the specified account a few times. +func UnlockAccount(accman *accounts.Manager, address string, i int, passwords []string) (accounts.Account, string) { + account, err := MakeAddress(accman, address) + if err != nil { + Fatalf("Could not list accounts: %v", err) + } + for trials := 0; trials < 3; trials++ { + prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) + password := GetPassPhrase(prompt, false, i, passwords) + err = accman.Unlock(account, password) + if err == nil { + glog.V(logger.Info).Infof("Unlocked account %x", account.Address) + return account, password + } + if err, ok := err.(*accounts.AmbiguousAddrError); ok { + glog.V(logger.Info).Infof("Unlocked account %x", account.Address) + return ambiguousAddrRecovery(accman, err, password), password + } + if err != accounts.ErrDecrypt { + // No need to prompt again if the error is not decryption-related. + break + } + } + // All trials expended to unlock account, bail out + Fatalf("Failed to unlock account %s (%v)", address, err) + return accounts.Account{}, "" +} + +func ambiguousAddrRecovery(am *accounts.Manager, err *accounts.AmbiguousAddrError, auth string) accounts.Account { + fmt.Printf("Multiple key files exist for address %x:\n", err.Addr) + for _, a := range err.Matches { + fmt.Println(" ", a.File) + } + fmt.Println("Testing your passphrase against all of them...") + var match *accounts.Account + for _, a := range err.Matches { + if err := am.Unlock(a, auth); err == nil { + match = &a + break + } + } + if match == nil { + Fatalf("None of the listed files could be unlocked.") + } + fmt.Printf("Your passphrase unlocked %s\n", match.File) + fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:") + for _, a := range err.Matches { + if a != *match { + fmt.Println(" ", a.File) + } + } + return *match +} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a5c2c15328..6b056b85ca 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -50,12 +50,6 @@ import ( "github.com/ethereum/go-ethereum/pow" "github.com/ethereum/go-ethereum/release" "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/rpc/api" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/comms" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/rpc/useragent" - rpc "github.com/ethereum/go-ethereum/rpc/v2" "github.com/ethereum/go-ethereum/swarm" bzzapi "github.com/ethereum/go-ethereum/swarm/api" "github.com/ethereum/go-ethereum/whisper" @@ -357,22 +351,6 @@ var ( Name: "shh", Usage: "Enable Whisper", } - ChequebookAddrFlag = cli.StringFlag{ - Name: "chequebook", - Usage: "chequebook contract address", - } - SwarmAccountAddrFlag = cli.StringFlag{ - Name: "bzzaccount", - Usage: "Swarm account address (swarm disabled if empty)", - } - SwarmConfigPathFlag = cli.StringFlag{ - Name: "bzzconfig", - Usage: "Swarm config file path (datadir/bzz)", - } - SwarmSwapDisabled = cli.BoolFlag{ - Name: "bzznoswap", - Usage: "Swarm SWAP disabled (false)", - } // ATM the url is left to the user and deployment to JSpathFlag = cli.StringFlag{ Name: "jspath", @@ -416,6 +394,32 @@ var ( Usage: "Suggested gas price base correction factor (%)", Value: 110, } + + // on top of develop + ChequebookAddrFlag = cli.StringFlag{ + Name: "chequebook", + Usage: "chequebook contract address", + } + SwarmAccountAddrFlag = cli.StringFlag{ + Name: "bzzaccount", + Usage: "Swarm account address (swarm disabled if empty)", + } + SwarmPortFlag = cli.StringFlag{ + Name: "bzzport", + Usage: "Swarm local http api port", + } + SwarmConfigPathFlag = cli.StringFlag{ + Name: "bzzconfig", + Usage: "Swarm config file path (datadir/bzz)", + } + SwarmSwapDisabled = cli.BoolFlag{ + Name: "bzznoswap", + Usage: "Swarm SWAP disabled (false)", + } + SwarmSyncDisabled = cli.BoolFlag{ + Name: "bzznosync", + Usage: "Swarm Syncing disabled (false)", + } ) // MustMakeDataDir retrieves the currently requested data directory, terminating @@ -664,53 +668,6 @@ func MakePasswordList(ctx *cli.Context) []string { return lines } -func UnlockAccount(ctx *cli.Context, accman *accounts.Manager, address string, i int, passwords []string) (common.Address, string) { - // Try to unlock the specified account a few times - account, err := MakeAddress(accman, address) - if err != nil { - Fatalf("unable to unlock account %v: %v", address, err) - } - - for trials := 0; trials < 3; trials++ { - prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3) - password := GetPassPhrase(prompt, false, i, passwords) - if err := accman.Unlock(account, password); err == nil { - return account, password - } - } - // All trials expended to unlock account, bail out - Fatalf("Failed to unlock account: %s", address) - return common.Address{}, "" -} - -// getPassPhrase retrieves the passwor associated with an account, either fetched -// from a list of preloaded passphrases, or requested interactively from the user. -func GetPassPhrase(prompt string, confirmation bool, i int, passwords []string) string { - // If a list of passwords was supplied, retrieve from them - if len(passwords) > 0 { - if i < len(passwords) { - return passwords[i] - } - return passwords[len(passwords)-1] - } - // Otherwise prompt the user for the password - fmt.Println(prompt) - password, err := PromptPassword("Passphrase: ", true) - if err != nil { - Fatalf("Failed to read passphrase: %v", err) - } - if confirmation { - confirm, err := PromptPassword("Repeat passphrase: ", false) - if err != nil { - Fatalf("Failed to read passphrase confirmation: %v", err) - } - if password != confirm { - Fatalf("Passphrases do not match") - } - } - return password -} - // MakeSystemNode sets up a local node, configures the services to launch and // assembles the P2P protocol stack. func MakeSystemNode(name, version string, relconf release.Config, extra []byte, ctx *cli.Context) *node.Node { @@ -724,6 +681,7 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte, if networks > 1 { Fatalf("The %v flags are mutually exclusive", netFlags) } + // Configure the node's service container datadir := MustMakeDataDir(ctx) netprv := MakeNodeKey(ctx) // Configure the node's service container @@ -754,7 +712,7 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte, accounts := strings.Split(ctx.GlobalString(UnlockedAccountFlag.Name), ",") for i, account := range accounts { if trimmed := strings.TrimSpace(account); trimmed != "" { - UnlockAccount(ctx, accman, trimmed, i, passwords) + UnlockAccount(accman, trimmed, i, passwords) } } @@ -844,15 +802,11 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte, if err != nil { Fatalf("Failed to create the protocol stack: %v", err) } - - // eth.Ethereum: ethereum if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { return eth.New(ctx, ethConf) }); err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } - - // Whisper if shhEnable { if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { Fatalf("Failed to register the Whisper service: %v", err) @@ -862,13 +816,14 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte, return release.NewReleaseService(ctx, relconf) }); err != nil { Fatalf("Failed to register the Geth release oracle service: %v", err) + } // bzz. Swarm var bzzconfig *bzzapi.Config hexaddr := ctx.GlobalString(SwarmAccountAddrFlag.Name) if hexaddr != "" { swarmaccount := common.HexToAddress(hexaddr) - if !accman.HasAccount(swarmaccount) { + if !accman.HasAddress(swarmaccount) { Fatalf("swarm account '%v' does not exist: %v", hexaddr, err) } prvkey, err := accman.GetUnlocked(swarmaccount) @@ -884,13 +839,19 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte, if err != nil { Fatalf("unable to configure swarm: %v", err) } - swap := ctx.GlobalBool(SwarmSwapDisabled.Name) + bzzport := ctx.GlobalString(SwarmPortFlag.Name) + if len(bzzport) > 0 { + bzzconfig.Port = bzzport + } + swapEnabled := !ctx.GlobalBool(SwarmSwapDisabled.Name) + syncEnabled := !ctx.GlobalBool(SwarmSyncDisabled.Name) if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { - return swarm.NewSwarm(ctx, bzzconfig, swap) + return swarm.NewSwarm(ctx, bzzconfig, swapEnabled, syncEnabled) }); err != nil { Fatalf("Failed to register the Swarm service: %v", err) } } + return stack } diff --git a/cmd/utils/input.go b/cmd/utils/input.go index 523d5a5870..21ed8b68de 100644 --- a/cmd/utils/input.go +++ b/cmd/utils/input.go @@ -96,3 +96,33 @@ func (r *userInputReader) ConfirmPrompt(prompt string) (bool, error) { } return false, err } + +// getPassPhrase retrieves the password associated with an account, either fetched +// from a list of preloaded passphrases, or requested interactively from the user. +func GetPassPhrase(prompt string, confirmation bool, i int, passwords []string) string { + // If a list of passwords was supplied, retrieve from them + if len(passwords) > 0 { + if i < len(passwords) { + return passwords[i] + } + return passwords[len(passwords)-1] + } + // Otherwise prompt the user for the password + if prompt != "" { + fmt.Println(prompt) + } + password, err := Stdin.PasswordPrompt("Passphrase: ") + if err != nil { + Fatalf("Failed to read passphrase: %v", err) + } + if confirmation { + confirm, err := Stdin.PasswordPrompt("Repeat passphrase: ") + if err != nil { + Fatalf("Failed to read passphrase confirmation: %v", err) + } + if password != confirm { + Fatalf("Passphrases do not match") + } + } + return password +} diff --git a/common/chequebook/contract.go b/common/chequebook/contract.go deleted file mode 100644 index 866d7c3358..0000000000 --- a/common/chequebook/contract.go +++ /dev/null @@ -1,63 +0,0 @@ -package chequebook - -import () - -const ( - ContractCode = `606060405260008054600160a060020a03191633179055610201806100246000396000f3606060405260e060020a600035046341c0e1b58114610031578063d75d691d14610059578063fbf788d61461007e575b005b61002f60005433600160a060020a03908116911614156101ff57600054600160a060020a0316ff5b600160a060020a03600435166000908152600160205260409020546060908152602090f35b61002f600435602435604435606435608435600160a060020a03851660009081526001602052604081205485116100b8575b505050505050565b30600160a060020a039081166c0100000000000000000000000090810260609081529188160260745260888690526048812080825260ff8616608090815260a086905260c0859052909260019260e0926020928290866161da5a03f11561000257505060405151600054600160a060020a03908116911614610139576100b0565b85600160a060020a031660006001600050600089600160a060020a03168152602001908152602001600020600050548703604051809050600060405180830381858888f19350505050156101b357846001600050600088600160a060020a03168152602001908152602001600020600050819055506100b0565b60005460408051600160a060020a03929092168252517f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f9789181900360200190a185600160a060020a0316ff5b56` - - ContractDeployedCode = `0x606060405260e060020a600035046341c0e1b58114610031578063d75d691d14610059578063fbf788d61461007e575b005b61002f60005433600160a060020a03908116911614156101ff57600054600160a060020a0316ff5b600160a060020a03600435166000908152600160205260409020546060908152602090f35b61002f600435602435604435606435608435600160a060020a03851660009081526001602052604081205485116100b8575b505050505050565b30600160a060020a039081166c0100000000000000000000000090810260609081529188160260745260888690526048812080825260ff8616608090815260a086905260c0859052909260019260e0926020928290866161da5a03f11561000257505060405151600054600160a060020a03908116911614610139576100b0565b85600160a060020a031660006001600050600089600160a060020a03168152602001908152602001600020600050548703604051809050600060405180830381858888f19350505050156101b357846001600050600088600160a060020a03168152602001908152602001600020600050819055506100b0565b60005460408051600160a060020a03929092168252517f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f9789181900360200190a185600160a060020a0316ff5b56` - - ContractAbi = `[{"constant":false,"inputs":[],"name":"kill","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"beneficiary","type":"address"}],"name":"getSent","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"beneficiary","type":"address"},{"name":"amount","type":"uint256"},{"name":"sig_v","type":"uint8"},{"name":"sig_r","type":"bytes32"},{"name":"sig_s","type":"bytes32"}],"name":"cash","outputs":[],"type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"deadbeat","type":"address"}],"name":"Overdraft","type":"event"}]` - - ContractSource = ` -import "mortal"; - -/// @title Chequebook for Ethereum micropayments -/// @author Daniel A. Nagy -contract chequebook is mortal { - // Cumulative paid amount in wei to each beneficiary - mapping (address => uint256) sent; - - /// @notice Overdraft event - event Overdraft(address deadbeat); - - /// @notice Accessor for sent map - /// - /// @param beneficiary beneficiary address - /// @return cumulative amount in wei sent to beneficiary - function getSent(address beneficiary) returns (uint256) { - return sent[beneficiary]; - } - - /// @notice Cash cheque - /// - /// @param beneficiary beneficiary address - /// @param amount cumulative amount in wei - /// @param sig_v signature parameter v - /// @param sig_r signature parameter r - /// @param sig_s signature parameter s - /// The digital signature is calculated on the concatenated triplet of contract address, beneficiary address and cumulative amount - function cash(address beneficiary, uint256 amount, - uint8 sig_v, bytes32 sig_r, bytes32 sig_s) { - // Check if the cheque is old. - // Only cheques that are more recent than the last cashed one are considered. - if(amount <= sent[beneficiary]) return; - // Check the digital signature of the cheque. - bytes32 hash = sha3(address(this), beneficiary, amount); - if(owner != ecrecover(hash, sig_v, sig_r, sig_s)) return; - // Attempt sending the difference between the cumulative amount on the cheque - // and the cumulative amount on the last cashed cheque to beneficiary. - if (beneficiary.send(amount - sent[beneficiary])) { - // Upon success, update the cumulative amount. - sent[beneficiary] = amount; - } else { - // Upon failure, punish owner for writing a bounced cheque. - // owner.sendToDebtorsPrison(); - Overdraft(owner); - // Compensate beneficiary. - suicide(beneficiary); - } - } -} -` -) diff --git a/common/registrar/contracts.go b/common/registrar/contracts.go deleted file mode 100644 index cd80dfcaba..0000000000 --- a/common/registrar/contracts.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package registrar - -const ( // built-in contracts address source code and evm code - UrlHintCode = "0x60c180600c6000396000f30060003560e060020a90048063300a3bbf14601557005b6024600435602435604435602a565b60006000f35b6000600084815260200190815260200160002054600160a060020a0316600014806078575033600160a060020a03166000600085815260200190815260200160002054600160a060020a0316145b607f5760bc565b336000600085815260200190815260200160002081905550806001600085815260200190815260200160002083610100811060b657005b01819055505b50505056" - - UrlHintSrc = ` -contract URLhint { - function register(uint256 _hash, uint8 idx, uint256 _url) { - if (owner[_hash] == 0 || owner[_hash] == msg.sender) { - owner[_hash] = msg.sender; - url[_hash][idx] = _url; - } - } - mapping (uint256 => address) owner; - (uint256 => uint256[256]) url; -} - ` - - HashRegCode = "0x609880600c6000396000f30060003560e060020a9004806331e12c2014601f578063d66d6c1014602b57005b6025603d565b60006000f35b6037600435602435605d565b60006000f35b600054600160a060020a0316600014605357605b565b336000819055505b565b600054600160a060020a031633600160a060020a031614607b576094565b8060016000848152602001908152602001600020819055505b505056" - - HashRegSrc = ` -contract HashReg { - function setowner() { - if (owner == 0) { - owner = msg.sender; - } - } - function register(uint256 _key, uint256 _content) { - if (msg.sender == owner) { - content[_key] = _content; - } - } - address owner; - mapping (uint256 => uint256) content; -} -` - - GlobalRegistrarSrc = ` -//sol - -import "owned"; - -contract NameRegister { - function addr(bytes32 _name) constant returns (address o_owner) {} - function name(address _owner) constant returns (bytes32 o_name) {} -} - -contract Registrar is NameRegister { - event Changed(bytes32 indexed name); - event PrimaryChanged(bytes32 indexed name, address indexed addr); - - function owner(bytes32 _name) constant returns (address o_owner) {} - function addr(bytes32 _name) constant returns (address o_address) {} - function subRegistrar(bytes32 _name) constant returns (address o_subRegistrar) {} - function content(bytes32 _name) constant returns (bytes32 o_content) {} - - function name(address _owner) constant returns (bytes32 o_name) {} -} - -contract GlobalRegistrar is Registrar { - struct Record { - address owner; - address primary; - address subRegistrar; - bytes32 content; - uint value; - uint renewalDate; - } - - function Registrar() { - // TODO: Populate with hall-of-fame. - } - - function reserve(bytes32 _name) { - // Don't allow the same name to be overwritten. - // TODO: bidding mechanism - if (m_toRecord[_name].owner == 0) { - m_toRecord[_name].owner = msg.sender; - Changed(_name); - } - } - - /* - TODO - > 12 chars: free - <= 12 chars: auction: - 1. new names are auctioned - - 7 day period to collect all bid bytes32es + deposits - - 1 day period to collect all bids to be considered (validity requires associated deposit to be >10% of bid) - - all valid bids are burnt except highest - difference between that and second highest is returned to winner - 2. remember when last auctioned/renewed - 3. anyone can force renewal process: - - 7 day period to collect all bid bytes32es + deposits - - 1 day period to collect all bids & full amounts - bids only uncovered if sufficiently high. - - 1% of winner burnt; original owner paid rest. - */ - - modifier onlyrecordowner(bytes32 _name) { if (m_toRecord[_name].owner == msg.sender) _ } - - function transfer(bytes32 _name, address _newOwner) onlyrecordowner(_name) { - m_toRecord[_name].owner = _newOwner; - Changed(_name); - } - - function disown(bytes32 _name) onlyrecordowner(_name) { - if (m_toName[m_toRecord[_name].primary] == _name) - { - PrimaryChanged(_name, m_toRecord[_name].primary); - m_toName[m_toRecord[_name].primary] = ""; - } - delete m_toRecord[_name]; - Changed(_name); - } - - function setAddress(bytes32 _name, address _a, bool _primary) onlyrecordowner(_name) { - m_toRecord[_name].primary = _a; - if (_primary) - { - PrimaryChanged(_name, _a); - m_toName[_a] = _name; - } - Changed(_name); - } - function setSubRegistrar(bytes32 _name, address _registrar) onlyrecordowner(_name) { - m_toRecord[_name].subRegistrar = _registrar; - Changed(_name); - } - function setContent(bytes32 _name, bytes32 _content) onlyrecordowner(_name) { - m_toRecord[_name].content = _content; - Changed(_name); - } - - function owner(bytes32 _name) constant returns (address) { return m_toRecord[_name].owner; } - function addr(bytes32 _name) constant returns (address) { return m_toRecord[_name].primary; } -// function subRegistrar(bytes32 _name) constant returns (address) { return m_toRecord[_name].subRegistrar; } // TODO: bring in on next iteration. - function register(bytes32 _name) constant returns (address) { return m_toRecord[_name].subRegistrar; } // only possible for now - function content(bytes32 _name) constant returns (bytes32) { return m_toRecord[_name].content; } - function name(address _owner) constant returns (bytes32 o_name) { return m_toName[_owner]; } - - mapping (address => bytes32) m_toName; - mapping (bytes32 => Record) m_toRecord; -} -` - GlobalRegistrarAbi = `[{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"name","outputs":[{"name":"o_name","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"owner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"content","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"addr","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"reserve","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"subRegistrar","outputs":[{"name":"o_subRegistrar","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_newOwner","type":"address"}],"name":"transfer","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_registrar","type":"address"}],"name":"setSubRegistrar","outputs":[],"type":"function"},{"constant":false,"inputs":[],"name":"Registrar","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_a","type":"address"},{"name":"_primary","type":"bool"}],"name":"setAddress","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"},{"name":"_content","type":"bytes32"}],"name":"setContent","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"bytes32"}],"name":"disown","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_name","type":"bytes32"}],"name":"register","outputs":[{"name":"","type":"address"}],"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"}],"name":"Changed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"bytes32"},{"indexed":true,"name":"addr","type":"address"}],"name":"PrimaryChanged","type":"event"}]` - - GlobalRegistrarCode = `0x610b408061000e6000396000f3006000357c01000000000000000000000000000000000000000000000000000000009004806301984892146100b357806302571be3146100ce5780632dff6941146100ff5780633b3b57de1461011a578063432ced041461014b5780635a3a05bd1461016257806379ce9fac1461019357806389a69c0e146101b0578063b9f37c86146101cd578063be99a980146101de578063c3d014d614610201578063d93e75731461021e578063e1fa8e841461023557005b6100c4600480359060200150610b02565b8060005260206000f35b6100df6004803590602001506109f3565b8073ffffffffffffffffffffffffffffffffffffffff1660005260206000f35b610110600480359060200150610ad4565b8060005260206000f35b61012b600480359060200150610a3e565b8073ffffffffffffffffffffffffffffffffffffffff1660005260206000f35b61015c600480359060200150610271565b60006000f35b610173600480359060200150610266565b8073ffffffffffffffffffffffffffffffffffffffff1660005260206000f35b6101aa600480359060200180359060200150610341565b60006000f35b6101c7600480359060200180359060200150610844565b60006000f35b6101d860045061026e565b60006000f35b6101fb6004803590602001803590602001803590602001506106de565b60006000f35b61021860048035906020018035906020015061092c565b60006000f35b61022f600480359060200150610429565b60006000f35b610246600480359060200150610a89565b8073ffffffffffffffffffffffffffffffffffffffff1660005260206000f35b60005b919050565b5b565b60006001600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561033d57336001600050600083815260200190815260200160002060005060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff02191690830217905550807fa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc6040604090036040a25b5b50565b813373ffffffffffffffffffffffffffffffffffffffff166001600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561042357816001600050600085815260200190815260200160002060005060000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff02191690830217905550827fa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc6040604090036040a25b505b5050565b803373ffffffffffffffffffffffffffffffffffffffff166001600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614156106d95781600060005060006001600050600086815260200190815260200160002060005060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000505414156105fd576001600050600083815260200190815260200160002060005060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16827ff63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a85456040604090036040a36000600060005060006001600050600086815260200190815260200160002060005060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600050819055505b6001600050600083815260200190815260200160002060006000820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556002820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556003820160005060009055600482016000506000905560058201600050600090555050817fa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc6040604090036040a25b505b50565b823373ffffffffffffffffffffffffffffffffffffffff166001600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561083d57826001600050600086815260200190815260200160002060005060010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908302179055508115610811578273ffffffffffffffffffffffffffffffffffffffff16847ff63780e752c6a54a94fc52715dbc5518a3b4c3c2833d301a204226548a2a85456040604090036040a383600060005060008573ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600050819055505b837fa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc6040604090036040a25b505b505050565b813373ffffffffffffffffffffffffffffffffffffffff166001600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561092657816001600050600085815260200190815260200160002060005060020160006101000a81548173ffffffffffffffffffffffffffffffffffffffff02191690830217905550827fa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc6040604090036040a25b505b5050565b813373ffffffffffffffffffffffffffffffffffffffff166001600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614156109ed57816001600050600085815260200190815260200160002060005060030160005081905550827fa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc6040604090036040a25b505b5050565b60006001600050600083815260200190815260200160002060005060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050610a39565b919050565b60006001600050600083815260200190815260200160002060005060010160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050610a84565b919050565b60006001600050600083815260200190815260200160002060005060020160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050610acf565b919050565b600060016000506000838152602001908152602001600020600050600301600050549050610afd565b919050565b6000600060005060008373ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600050549050610b3b565b91905056` -) diff --git a/common/registrar/ethreg/api.go b/common/registrar/ethreg/api.go deleted file mode 100644 index 6d77a9385d..0000000000 --- a/common/registrar/ethreg/api.go +++ /dev/null @@ -1,279 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package ethreg - -import ( - "errors" - "math/big" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/compiler" - "github.com/ethereum/go-ethereum/common/registrar" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" -) - -// registryAPIBackend is a backend for an Ethereum Registry. -type registryAPIBackend struct { - config *core.ChainConfig - bc *core.BlockChain - chainDb ethdb.Database - txPool *core.TxPool - am *accounts.Manager -} - -// PrivateRegistarAPI offers various functions to access the Ethereum registry. -type PrivateRegistarAPI struct { - config *core.ChainConfig - be *registryAPIBackend -} - -// NewPrivateRegistarAPI creates a new PrivateRegistarAPI instance. -func NewPrivateRegistarAPI(config *core.ChainConfig, bc *core.BlockChain, chainDb ethdb.Database, txPool *core.TxPool, am *accounts.Manager) *PrivateRegistarAPI { - return &PrivateRegistarAPI{ - config: config, - be: ®istryAPIBackend{ - config: config, - bc: bc, - chainDb: chainDb, - txPool: txPool, - am: am, - }, - } -} - -// SetGlobalRegistrar allows clients to set the global registry for the node. -// This method can be used to deploy a new registry. First zero out the current -// address by calling the method with namereg = '0x0' and then call this method -// again with '' as namereg. This will submit a transaction to the network which -// will deploy a new registry on execution. The TX hash is returned. When called -// with namereg '' and the current address is not zero the current global is -// address is returned.. -func (api *PrivateRegistarAPI) SetGlobalRegistrar(namereg string, from common.Address) (string, error) { - return registrar.New(api.be).SetGlobalRegistrar(namereg, from) -} - -// SetHashReg queries the registry for a hash. -func (api *PrivateRegistarAPI) SetHashReg(hashreg string, from common.Address) (string, error) { - return registrar.New(api.be).SetHashReg(hashreg, from) -} - -// SetUrlHint queries the registry for an url. -func (api *PrivateRegistarAPI) SetUrlHint(hashreg string, from common.Address) (string, error) { - return registrar.New(api.be).SetUrlHint(hashreg, from) -} - -// SaveInfo stores contract information on the local file system. -func (api *PrivateRegistarAPI) SaveInfo(info *compiler.ContractInfo, filename string) (contenthash common.Hash, err error) { - return compiler.SaveInfo(info, filename) -} - -// Register registers a new content hash in the registry. -func (api *PrivateRegistarAPI) Register(sender common.Address, addr common.Address, contentHashHex string) (bool, error) { - block := api.be.bc.CurrentBlock() - state, err := state.New(block.Root(), api.be.chainDb) - if err != nil { - return false, err - } - - codeb := state.GetCode(addr) - codeHash := common.BytesToHash(crypto.Keccak256(codeb)) - contentHash := common.HexToHash(contentHashHex) - - _, err = registrar.New(api.be).SetHashToHash(sender, codeHash, contentHash) - return err == nil, err -} - -// RegisterUrl registers a new url in the registry. -func (api *PrivateRegistarAPI) RegisterUrl(sender common.Address, contentHashHex string, url string) (bool, error) { - _, err := registrar.New(api.be).SetUrlToHash(sender, common.HexToHash(contentHashHex), url) - return err == nil, err -} - -// callmsg is the message type used for call transations. -type callmsg struct { - from *state.StateObject - to *common.Address - gas, gasPrice *big.Int - value *big.Int - data []byte -} - -// accessor boilerplate to implement core.Message -func (m callmsg) From() (common.Address, error) { - return m.from.Address(), nil -} -func (m callmsg) FromFrontier() (common.Address, error) { - return m.from.Address(), nil -} -func (m callmsg) Nonce() uint64 { - return m.from.Nonce() -} -func (m callmsg) To() *common.Address { - return m.to -} -func (m callmsg) GasPrice() *big.Int { - return m.gasPrice -} -func (m callmsg) Gas() *big.Int { - return m.gas -} -func (m callmsg) Value() *big.Int { - return m.value -} -func (m callmsg) Data() []byte { - return m.data -} - -// Call forms a transaction from the given arguments and tries to execute it on -// a private VM with a copy of the state. Any changes are therefore only temporary -// and not part of the actual state. This allows for local execution/queries. -func (be *registryAPIBackend) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, string, error) { - block := be.bc.CurrentBlock() - statedb, err := state.New(block.Root(), be.chainDb) - if err != nil { - return "", "", err - } - - var from *state.StateObject - if len(fromStr) == 0 { - accounts := be.am.Accounts() - if len(accounts) == 0 { - from = statedb.GetOrNewStateObject(common.Address{}) - } else { - from = statedb.GetOrNewStateObject(accounts[0].Address) - } - } else { - from = statedb.GetOrNewStateObject(common.HexToAddress(fromStr)) - } - - from.SetBalance(common.MaxBig) - - msg := callmsg{ - from: from, - gas: common.Big(gasStr), - gasPrice: common.Big(gasPriceStr), - value: common.Big(valueStr), - data: common.FromHex(dataStr), - } - if len(toStr) > 0 { - addr := common.HexToAddress(toStr) - msg.to = &addr - } - - if msg.gas.Cmp(big.NewInt(0)) == 0 { - msg.gas = big.NewInt(50000000) - } - - if msg.gasPrice.Cmp(big.NewInt(0)) == 0 { - msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) - } - - header := be.bc.CurrentBlock().Header() - vmenv := core.NewEnv(statedb, be.config, be.bc, msg, header, vm.Config{}) - gp := new(core.GasPool).AddGas(common.MaxBig) - res, gas, err := core.ApplyMessage(vmenv, msg, gp) - - return common.ToHex(res), gas.String(), err -} - -// StorageAt returns the data stores in the state for the given address and location. -func (be *registryAPIBackend) StorageAt(addr string, storageAddr string) string { - block := be.bc.CurrentBlock() - state, err := state.New(block.Root(), be.chainDb) - if err != nil { - return "" - } - return state.GetState(common.HexToAddress(addr), common.HexToHash(storageAddr)).Hex() -} - -// Transact forms a transaction from the given arguments and submits it to the -// transactio pool for execution. -func (be *registryAPIBackend) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { - if len(toStr) > 0 && toStr != "0x" && !common.IsHexAddress(toStr) { - return "", errors.New("invalid address") - } - - var ( - from = common.HexToAddress(fromStr) - to = common.HexToAddress(toStr) - value = common.Big(valueStr) - gas *big.Int - price *big.Int - data []byte - contractCreation bool - ) - - if len(gasStr) == 0 { - gas = big.NewInt(90000) - } else { - gas = common.Big(gasStr) - } - - if len(gasPriceStr) == 0 { - price = big.NewInt(10000000000000) - } else { - price = common.Big(gasPriceStr) - } - - data = common.FromHex(codeStr) - if len(toStr) == 0 { - contractCreation = true - } - - nonce := be.txPool.State().GetNonce(from) - if len(nonceStr) != 0 { - nonce = common.Big(nonceStr).Uint64() - } - - var tx *types.Transaction - if contractCreation { - tx = types.NewContractCreation(nonce, value, gas, price, data) - } else { - tx = types.NewTransaction(nonce, to, value, gas, price, data) - } - - signature, err := be.am.Sign(from, tx.SigHash().Bytes()) - if err != nil { - return "", err - } - signedTx, err := tx.WithSignature(signature) - if err != nil { - return "", err - } - - be.txPool.SetLocal(signedTx) - if err := be.txPool.Add(signedTx); err != nil { - return "", nil - } - - if contractCreation { - addr := crypto.CreateAddress(from, nonce) - glog.V(logger.Info).Infof("Tx(%s) created: %s\n", signedTx.Hash().Hex(), addr.Hex()) - } else { - glog.V(logger.Info).Infof("Tx(%s) to: %s\n", signedTx.Hash().Hex(), tx.To().Hex()) - } - - return signedTx.Hash().Hex(), nil -} diff --git a/common/registrar/registrar.go b/common/registrar/registrar.go deleted file mode 100644 index 0606f6985f..0000000000 --- a/common/registrar/registrar.go +++ /dev/null @@ -1,436 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package registrar - -import ( - "encoding/binary" - "fmt" - "math/big" - "regexp" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" -) - -/* -Registrar implements the Ethereum name registrar services mapping -- arbitrary strings to ethereum addresses -- hashes to hashes -- hashes to arbitrary strings -(likely will provide lookup service for all three) - -The Registrar is used by -* the roundtripper transport implementation of -url schemes to resolve domain names and services that register these names -* contract info retrieval (NatSpec). - -The Registrar uses 3 contracts on the blockchain: -* GlobalRegistrar: Name (string) -> Address (Owner) -* HashReg : Key Hash (hash of domain name or contract code) -> Content Hash -* UrlHint : Content Hash -> Url Hint - -These contracts are (currently) not included in the genesis block. -Each Set needs to be called once on each blockchain/network once. - -Contract addresses need to be set the first time any Registrar method is called -in a client session. -This is done for frontier by default, otherwise the caller needs to make sure -the relevant environment initialised the desired contracts -*/ -var ( - // GlobalRegistrarAddr = "0xc6d9d2cd449a754c494264e1809c50e34d64562b" // olympic - GlobalRegistrarAddr = "0x33990122638b9132ca29c723bdf037f1a891a70c" // frontier - HashRegAddr = "0x23bf622b5a65f6060d855fca401133ded3520620" // frontier - UrlHintAddr = "0x73ed5ef6c010727dfd2671dbb70faac19ec18626" // frontier - - zero = regexp.MustCompile("^(0x)?0*$") -) - -const ( - trueHex = "0000000000000000000000000000000000000000000000000000000000000001" - falseHex = "0000000000000000000000000000000000000000000000000000000000000000" -) - -func abiSignature(s string) string { - return common.ToHex(crypto.Keccak256([]byte(s))[:4]) -} - -var ( - HashRegName = "HashReg" - UrlHintName = "UrlHint" - - registerContentHashAbi = abiSignature("register(uint256,uint256)") - registerUrlAbi = abiSignature("register(uint256,uint8,uint256)") - setOwnerAbi = abiSignature("setowner()") - reserveAbi = abiSignature("reserve(bytes32)") - resolveAbi = abiSignature("addr(bytes32)") - registerAbi = abiSignature("setAddress(bytes32,address,bool)") - addressAbiPrefix = falseHex[:24] -) - -// Registrar's backend is defined as an interface (implemented by xeth, but could be remote) -type Backend interface { - StorageAt(string, string) string - Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) - Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error) -} - -// TODO Registrar should also just implement The Resolver and Registry interfaces -// Simplify for now. -type VersionedRegistrar interface { - Resolver(*big.Int) *Registrar - Registry() *Registrar -} - -type Registrar struct { - backend Backend -} - -func New(b Backend) (res *Registrar) { - res = &Registrar{b} - return -} - -func (self *Registrar) SetGlobalRegistrar(namereg string, addr common.Address) (txhash string, err error) { - if namereg != "" { - GlobalRegistrarAddr = namereg - return - } - if zero.MatchString(GlobalRegistrarAddr) { - if (addr == common.Address{}) { - err = fmt.Errorf("GlobalRegistrar address not found and sender for creation not given") - return - } else { - txhash, err = self.backend.Transact(addr.Hex(), "", "", "", "800000", "", GlobalRegistrarCode) - if err != nil { - err = fmt.Errorf("GlobalRegistrar address not found and sender for creation failed: %v", err) - return - } - } - } - return -} - -func (self *Registrar) SetHashReg(hashreg string, addr common.Address) (txhash string, err error) { - if hashreg != "" { - HashRegAddr = hashreg - } else { - if !zero.MatchString(HashRegAddr) { - return - } - nameHex, extra := encodeName(HashRegName, 2) - hashRegAbi := resolveAbi + nameHex + extra - glog.V(logger.Detail).Infof("\ncall HashRegAddr %v with %v\n", GlobalRegistrarAddr, hashRegAbi) - var res string - res, _, err = self.backend.Call("", GlobalRegistrarAddr, "", "", "", hashRegAbi) - if len(res) >= 40 { - HashRegAddr = "0x" + res[len(res)-40:len(res)] - } - if err != nil || zero.MatchString(HashRegAddr) { - if (addr == common.Address{}) { - err = fmt.Errorf("HashReg address not found and sender for creation not given") - return - } - - txhash, err = self.backend.Transact(addr.Hex(), "", "", "", "", "", HashRegCode) - if err != nil { - err = fmt.Errorf("HashReg address not found and sender for creation failed: %v", err) - } - glog.V(logger.Detail).Infof("created HashRegAddr @ txhash %v\n", txhash) - } else { - glog.V(logger.Detail).Infof("HashRegAddr found at @ %v\n", HashRegAddr) - return - } - } - - return -} - -func (self *Registrar) SetUrlHint(urlhint string, addr common.Address) (txhash string, err error) { - if urlhint != "" { - UrlHintAddr = urlhint - } else { - if !zero.MatchString(UrlHintAddr) { - return - } - nameHex, extra := encodeName(UrlHintName, 2) - urlHintAbi := resolveAbi + nameHex + extra - glog.V(logger.Detail).Infof("UrlHint address query data: %s to %s", urlHintAbi, GlobalRegistrarAddr) - var res string - res, _, err = self.backend.Call("", GlobalRegistrarAddr, "", "", "", urlHintAbi) - if len(res) >= 40 { - UrlHintAddr = "0x" + res[len(res)-40:len(res)] - } - if err != nil || zero.MatchString(UrlHintAddr) { - if (addr == common.Address{}) { - err = fmt.Errorf("UrlHint address not found and sender for creation not given") - return - } - txhash, err = self.backend.Transact(addr.Hex(), "", "", "", "210000", "", UrlHintCode) - if err != nil { - err = fmt.Errorf("UrlHint address not found and sender for creation failed: %v", err) - } - glog.V(logger.Detail).Infof("created UrlHint @ txhash %v\n", txhash) - } else { - glog.V(logger.Detail).Infof("UrlHint found @ %v\n", HashRegAddr) - return - } - } - - return -} - -// ReserveName(from, name) reserves name for the sender address in the globalRegistrar -// the tx needs to be mined to take effect -func (self *Registrar) ReserveName(address common.Address, name string) (txh string, err error) { - if zero.MatchString(GlobalRegistrarAddr) { - return "", fmt.Errorf("GlobalRegistrar address is not set") - } - nameHex, extra := encodeName(name, 2) - abi := reserveAbi + nameHex + extra - glog.V(logger.Detail).Infof("Reserve data: %s", abi) - return self.backend.Transact( - address.Hex(), - GlobalRegistrarAddr, - "", "", "", "", - abi, - ) -} - -// SetAddressToName(from, name, addr) will set the Address to address for name -// in the globalRegistrar using from as the sender of the transaction -// the tx needs to be mined to take effect -func (self *Registrar) SetAddressToName(from common.Address, name string, address common.Address) (txh string, err error) { - if zero.MatchString(GlobalRegistrarAddr) { - return "", fmt.Errorf("GlobalRegistrar address is not set") - } - - nameHex, extra := encodeName(name, 6) - addrHex := encodeAddress(address) - - abi := registerAbi + nameHex + addrHex + trueHex + extra - glog.V(logger.Detail).Infof("SetAddressToName data: %s to %s ", abi, GlobalRegistrarAddr) - - return self.backend.Transact( - from.Hex(), - GlobalRegistrarAddr, - "", "", "", "", - abi, - ) -} - -// NameToAddr(from, name) queries the registrar for the address on name -func (self *Registrar) NameToAddr(from common.Address, name string) (address common.Address, err error) { - if zero.MatchString(GlobalRegistrarAddr) { - return address, fmt.Errorf("GlobalRegistrar address is not set") - } - - nameHex, extra := encodeName(name, 2) - abi := resolveAbi + nameHex + extra - glog.V(logger.Detail).Infof("NameToAddr data: %s", abi) - res, _, err := self.backend.Call( - from.Hex(), - GlobalRegistrarAddr, - "", "", "", - abi, - ) - if err != nil { - return - } - address = common.HexToAddress(res) - return -} - -// called as first step in the registration process on HashReg -func (self *Registrar) SetOwner(address common.Address) (txh string, err error) { - if zero.MatchString(HashRegAddr) { - return "", fmt.Errorf("HashReg address is not set") - } - return self.backend.Transact( - address.Hex(), - HashRegAddr, - "", "", "", "", - setOwnerAbi, - ) -} - -// registers some content hash to a key/code hash -// e.g., the contract Info combined Json Doc's ContentHash -// to CodeHash of a contract or hash of a domain -func (self *Registrar) SetHashToHash(address common.Address, codehash, dochash common.Hash) (txh string, err error) { - if zero.MatchString(HashRegAddr) { - return "", fmt.Errorf("HashReg address is not set") - } - - _, err = self.SetOwner(address) - if err != nil { - return - } - codehex := common.Bytes2Hex(codehash[:]) - dochex := common.Bytes2Hex(dochash[:]) - - data := registerContentHashAbi + codehex + dochex - glog.V(logger.Detail).Infof("SetHashToHash data: %s sent to %v\n", data, HashRegAddr) - return self.backend.Transact( - address.Hex(), - HashRegAddr, - "", "", "", "", - data, - ) -} - -// SetUrlToHash(from, hash, url) registers a url to a content hash so that the content can be fetched -// address is used as sender for the transaction and will be the owner of a new -// registry entry on first time use -// FIXME: silently doing nothing if sender is not the owner -// note that with content addressed storage, this step is no longer necessary -func (self *Registrar) SetUrlToHash(address common.Address, hash common.Hash, url string) (txh string, err error) { - if zero.MatchString(UrlHintAddr) { - return "", fmt.Errorf("UrlHint address is not set") - } - - hashHex := common.Bytes2Hex(hash[:]) - var urlHex string - urlb := []byte(url) - var cnt byte - n := len(urlb) - - for n > 0 { - if n > 32 { - n = 32 - } - urlHex = common.Bytes2Hex(urlb[:n]) - urlb = urlb[n:] - n = len(urlb) - bcnt := make([]byte, 32) - bcnt[31] = cnt - data := registerUrlAbi + - hashHex + - common.Bytes2Hex(bcnt) + - common.Bytes2Hex(common.Hex2BytesFixed(urlHex, 32)) - txh, err = self.backend.Transact( - address.Hex(), - UrlHintAddr, - "", "", "", "", - data, - ) - if err != nil { - return - } - cnt++ - } - return -} - -// HashToHash(key) resolves contenthash for key (a hash) using HashReg -// resolution is costless non-transactional -// implemented as direct retrieval from db -func (self *Registrar) HashToHash(khash common.Hash) (chash common.Hash, err error) { - if zero.MatchString(HashRegAddr) { - return common.Hash{}, fmt.Errorf("HashReg address is not set") - } - - // look up in hashReg - at := HashRegAddr[2:] - key := storageAddress(storageMapping(storageIdx2Addr(1), khash[:])) - hash := self.backend.StorageAt(at, key) - - if hash == "0x0" || len(hash) < 3 || (hash == common.Hash{}.Hex()) { - err = fmt.Errorf("HashToHash: content hash not found for '%v'", khash.Hex()) - return - } - copy(chash[:], common.Hex2BytesFixed(hash[2:], 32)) - return -} - -// HashToUrl(contenthash) resolves the url for contenthash using UrlHint -// resolution is costless non-transactional -// implemented as direct retrieval from db -// if we use content addressed storage, this step is no longer necessary -func (self *Registrar) HashToUrl(chash common.Hash) (uri string, err error) { - if zero.MatchString(UrlHintAddr) { - return "", fmt.Errorf("UrlHint address is not set") - } - // look up in URL reg - var str string = " " - var idx uint32 - for len(str) > 0 { - mapaddr := storageMapping(storageIdx2Addr(1), chash[:]) - key := storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(idx))) - hex := self.backend.StorageAt(UrlHintAddr[2:], key) - str = string(common.Hex2Bytes(hex[2:])) - l := 0 - for (l < len(str)) && (str[l] == 0) { - l++ - } - - str = str[l:] - uri = uri + str - idx++ - } - - if len(uri) == 0 { - err = fmt.Errorf("HashToUrl: URL hint not found for '%v'", chash.Hex()) - } - return -} - -func storageIdx2Addr(varidx uint32) []byte { - data := make([]byte, 32) - binary.BigEndian.PutUint32(data[28:32], varidx) - return data -} - -func storageMapping(addr, key []byte) []byte { - data := make([]byte, 64) - copy(data[0:32], key[0:32]) - copy(data[32:64], addr[0:32]) - sha := crypto.Keccak256(data) - return sha -} - -func storageFixedArray(addr, idx []byte) []byte { - var carry byte - for i := 31; i >= 0; i-- { - var b byte = addr[i] + idx[i] + carry - if b < addr[i] { - carry = 1 - } else { - carry = 0 - } - addr[i] = b - } - return addr -} - -func storageAddress(addr []byte) string { - return common.ToHex(addr) -} - -func encodeAddress(address common.Address) string { - return addressAbiPrefix + address.Hex()[2:] -} - -func encodeName(name string, index uint8) (string, string) { - extra := common.Bytes2Hex([]byte(name)) - if len(name) > 32 { - return fmt.Sprintf("%064x", index), extra - } - return extra + falseHex[len(extra):], "" -} diff --git a/common/registrar/registrar_test.go b/common/registrar/registrar_test.go deleted file mode 100644 index b2287803c2..0000000000 --- a/common/registrar/registrar_test.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package registrar - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -type testBackend struct { - // contracts mock - contracts map[string](map[string]string) -} - -var ( - text = "test" - codehash = common.StringToHash("1234") - hash = common.BytesToHash(crypto.Keccak256([]byte(text))) - url = "bzz://bzzhash/my/path/contr.act" -) - -func NewTestBackend() *testBackend { - self := &testBackend{} - self.contracts = make(map[string](map[string]string)) - return self -} - -func (self *testBackend) initHashReg() { - self.contracts[HashRegAddr[2:]] = make(map[string]string) - key := storageAddress(storageMapping(storageIdx2Addr(1), codehash[:])) - self.contracts[HashRegAddr[2:]][key] = hash.Hex() -} - -func (self *testBackend) initUrlHint() { - self.contracts[UrlHintAddr[2:]] = make(map[string]string) - mapaddr := storageMapping(storageIdx2Addr(1), hash[:]) - - key := storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(0))) - self.contracts[UrlHintAddr[2:]][key] = common.ToHex([]byte(url)) - key = storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(1))) - self.contracts[UrlHintAddr[2:]][key] = "0x0" -} - -func (self *testBackend) StorageAt(ca, sa string) (res string) { - c := self.contracts[ca] - if c == nil { - return "0x0" - } - res = c[sa] - return -} - -func (self *testBackend) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { - return "", nil -} - -func (self *testBackend) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error) { - return "", "", nil -} - -func TestSetGlobalRegistrar(t *testing.T) { - b := NewTestBackend() - res := New(b) - _, err := res.SetGlobalRegistrar("addresshex", common.BigToAddress(common.Big1)) - if err != nil { - t.Errorf("unexpected error: %v'", err) - } -} - -func TestHashToHash(t *testing.T) { - b := NewTestBackend() - res := New(b) - - HashRegAddr = "0x0" - got, err := res.HashToHash(codehash) - if err == nil { - t.Errorf("expected error") - } else { - exp := "HashReg address is not set" - if err.Error() != exp { - t.Errorf("incorrect error, expected '%v', got '%v'", exp, err.Error()) - } - } - - HashRegAddr = common.BigToAddress(common.Big1).Hex() //[2:] - got, err = res.HashToHash(codehash) - if err == nil { - t.Errorf("expected error") - } else { - exp := "HashToHash: content hash not found for '" + codehash.Hex() + "'" - if err.Error() != exp { - t.Errorf("incorrect error, expected '%v', got '%v'", exp, err.Error()) - } - } - - b.initHashReg() - got, err = res.HashToHash(codehash) - if err != nil { - t.Errorf("expected no error, got %v", err) - } else { - if got != hash { - t.Errorf("incorrect result, expected '%v', got '%v'", hash.Hex(), got.Hex()) - } - } -} - -func TestHashToUrl(t *testing.T) { - b := NewTestBackend() - res := New(b) - - UrlHintAddr = "0x0" - got, err := res.HashToUrl(hash) - if err == nil { - t.Errorf("expected error") - } else { - exp := "UrlHint address is not set" - if err.Error() != exp { - t.Errorf("incorrect error, expected '%v', got '%v'", exp, err.Error()) - } - } - - UrlHintAddr = common.BigToAddress(common.Big2).Hex() //[2:] - got, err = res.HashToUrl(hash) - if err == nil { - t.Errorf("expected error") - } else { - exp := "HashToUrl: URL hint not found for '" + hash.Hex() + "'" - if err.Error() != exp { - t.Errorf("incorrect error, expected '%v', got '%v'", exp, err.Error()) - } - } - - b.initUrlHint() - got, err = res.HashToUrl(hash) - if err != nil { - t.Errorf("expected no error, got %v", err) - } else { - if got != url { - t.Errorf("incorrect result, expected '%v', got '%s'", url, got) - } - } -} diff --git a/crypto/crypto.go b/crypto/crypto.go index 85f0970956..a17990da66 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -196,9 +196,15 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) { if len(hash) != 32 { return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(hash)) } + if prv.D == nil { + panic("prv.D is nil") + } + if prv.Params() == nil { + panic("prv.Params() is nil") + } seckey := common.LeftPadBytes(prv.D.Bytes(), prv.Params().BitSize/8) - defer zeroBytes(seckey) + // defer zeroBytes(seckey) sig, err = secp256k1.Sign(hash, seckey) return } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 58b29da490..0468e283d7 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -89,6 +89,7 @@ func TestSign(t *testing.T) { if err != nil { t.Errorf("Sign error: %s", err) } + recoveredPub, err := Ecrecover(msg, sig) if err != nil { t.Errorf("ECRecover error: %s", err) diff --git a/crypto/secp256k1/curve_test.go b/crypto/secp256k1/curve_test.go index 850cc748b8..d915ee8525 100644 --- a/crypto/secp256k1/curve_test.go +++ b/crypto/secp256k1/curve_test.go @@ -21,8 +21,6 @@ import ( "encoding/hex" "math/big" "testing" - - "github.com/ethereum/go-ethereum/common/registrar" ) func TestReadBits(t *testing.T) { @@ -39,34 +37,3 @@ func TestReadBits(t *testing.T) { check("0000000000012345000000000000000000000000000000000000FEFCF3F8F0") check("18F8F8F1000111000110011100222004330052300000000000000000FEFCF3F8F0") } - -type Backend interface { - registrar.Backend - AtStateNum(int64) registrar.Backend -} - -// implements a versioned Registrar on an archiving full node -type EthReg struct { - backend Backend - registry *registrar.Registrar -} - -func New(backend Backend) (self *EthReg) { - self = &EthReg{backend: backend} - self.registry = registrar.New(backend) - return -} - -func (self *EthReg) Registry() *registrar.Registrar { - return self.registry -} - -func (self *EthReg) Resolver(n *big.Int) *registrar.Registrar { - var s registrar.Backend - if n != nil { - s = self.backend.AtStateNum(n.Int64()) - } else { - s = registrar.Backend(self.backend) - } - return registrar.New(s) -} diff --git a/eth/backend.go b/eth/backend.go index f43dea7775..8dd56b91d5 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -33,7 +33,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/common/httpclient" - "github.com/ethereum/go-ethereum/common/registrar/ethreg" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -342,16 +341,12 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "debug", Version: "1.0", - Service: NewPrivateDebugAPI(s.chainConfig, s), + Service: NewPrivateDebugAPI(s.ChainConfig(), s), }, { Namespace: "net", Version: "1.0", Service: s.netRPCService, Public: true, - }, { - Namespace: "admin", - Version: "1.0", - Service: ethreg.NewPrivateRegistarAPI(s.chainConfig, s.blockchain, s.chainDb, s.txPool, s.accountManager), }, } } @@ -384,6 +379,8 @@ func (s *Ethereum) Miner() *miner.Miner { return s.miner } func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } +func (s *Ethereum) GPO() *GasPriceOracle { return s.gpo } +func (s *Ethereum) ChainConfig() *core.ChainConfig { return s.chainConfig } func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } diff --git a/eth/bind.go b/eth/bind.go index 3a3eca0623..95415d1285 100644 --- a/eth/bind.go +++ b/eth/bind.go @@ -21,6 +21,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" @@ -37,6 +39,7 @@ type ContractBackend struct { eapi *PublicEthereumAPI // Wrapper around the Ethereum object to access metadata bcapi *PublicBlockChainAPI // Wrapper around the blockchain to access chain data txapi *PublicTransactionPoolAPI // Wrapper around the transaction pool to access transaction data + eth *Ethereum } // NewContractBackend creates a new native contract backend using an existing @@ -46,6 +49,7 @@ func NewContractBackend(eth *Ethereum) *ContractBackend { eapi: NewPublicEthereumAPI(eth), bcapi: NewPublicBlockChainAPI(eth.chainConfig, eth.blockchain, eth.miner, eth.chainDb, eth.gpo, eth.eventMux, eth.accountManager), txapi: NewPublicTransactionPoolAPI(eth), + eth: eth, } } @@ -108,3 +112,20 @@ func (b *ContractBackend) SendTransaction(tx *types.Transaction) error { _, err := b.txapi.SendRawTransaction(common.ToHex(raw)) return err } + +func (b *ContractBackend) GetTxReceipt(txhash common.Hash) *types.Receipt { + return core.GetReceipt(b.eth.ChainDb(), txhash) +} + +func (b *ContractBackend) BalanceAt(address common.Address) *big.Int { + currentState, err := state.New(b.eth.BlockChain().CurrentBlock().Root(), b.eth.ChainDb()) + if err != nil { + return new(big.Int) + } + return currentState.GetBalance(address) +} + +func (b *ContractBackend) CodeAt(address common.Address) string { + currentState, _ := state.New(b.eth.BlockChain().CurrentBlock().Root(), b.eth.ChainDb()) + return common.ToHex(currentState.GetCode(address)) +} diff --git a/eth/downloader/modes.go b/eth/downloader/modes.go index 743d827c76..ec339c0740 100644 --- a/eth/downloader/modes.go +++ b/eth/downloader/modes.go @@ -23,26 +23,4 @@ const ( FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks FastSync // Quickly download the headers, full sync only at the chain head LightSync // Download only the headers and terminate afterwards - AdminApiName = "admin" - BzzApiName = "bzz" - EthApiName = "eth" - DbApiName = "db" - DebugApiName = "debug" - MergedApiName = "merged" - MinerApiName = "miner" - NetApiName = "net" - ShhApiName = "shh" - TxPoolApiName = "txpool" - PersonalApiName = "personal" - Web3ApiName = "web3" - - JsonRpcVersion = "2.0" -) - -var ( - // All API's - AllApis = strings.Join([]string{ - AdminApiName, BzzApiName, DbApiName, EthApiName, DebugApiName, MinerApiName, NetApiName, - ShhApiName, TxPoolApiName, PersonalApiName, Web3ApiName, - }, ",") ) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 64c1b5044b..c217443214 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -18,17 +18,165 @@ package web3ext var Modules = map[string]string{ - "txpool": TxPool_JS, - "admin": Admin_JS, - "personal": Personal_JS, - "eth": Eth_JS, - "miner": Miner_JS, - "debug": Debug_JS, - "net": Net_JS, + "txpool": TxPool_JS, + "admin": Admin_JS, + "personal": Personal_JS, + "eth": Eth_JS, + "miner": Miner_JS, + "debug": Debug_JS, + "net": Net_JS, + "bzz": Bzz_JS, + "ens": ENS_JS, + "chequebook": Chequebook_JS, } -const TxPool_JS = ` +const Bzz_JS = ` web3._extend({ + property: 'bzz', + methods: + [ + new web3._extend.Method({ + name: 'blockNetworkRead', + call: 'bzz_blockNetworkRead', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'syncEnabled', + call: 'bzz_syncEnabled', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'swapEnabled', + call: 'bzz_swapEnabled', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'download', + call: 'bzz_download', + params: 2, + inputFormatter: [null, null] + }), + new web3._extend.Method({ + name: 'upload', + call: 'bzz_upload', + params: 2, + inputFormatter: [null, null] + }), + new web3._extend.Method({ + name: 'retrieve', + call: 'bzz_retrieve', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'store', + call: 'bzz_store', + params: 2, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'get', + call: 'bzz_get', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'put', + call: 'bzz_put', + params: 2, + inputFormatter: [null, null] + }), + new web3._extend.Method({ + name: 'modify', + call: 'bzz_modify', + params: 4, + inputFormatter: [null, null, null, null] + }) + ], + properties: + [ + new web3._extend.Property({ + name: 'hive', + getter: 'bzz_hive' + }), + new web3._extend.Property({ + name: 'info', + getter: 'bzz_info', + }), + ] +}); +` + +const ENS_JS = ` +web3._extend({ + property: 'ens', + methods: + [ new web3._extend.Method({ + name: 'register', + call: 'ens_register', + params: 2, + inputFormatter: [null, null] + }), + new web3._extend.Method({ + name: 'resolve', + call: 'ens_resolve', + params: 1, + inputFormatter: [null] + }), + ] +}) +` + +const Chequebook_JS = ` +web3._extend({ + property: 'chequebook', + methods: + [ + new web3._extend.Method({ + name: 'deposit', + call: 'chequebook_deposit', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Property({ + name: 'balance', + getter: 'chequebook_balance', + outputFormatter: web3._extend.utils.toDecimal + }), + new web3._extend.Method({ + name: 'cash', + call: 'chequebook_cash', + params: 1, + inputFormatter: [null] + }), + new web3._extend.Method({ + name: 'issue', + call: 'chequebook_issue', + params: 2, + inputFormatter: [null, null] + }), + ] +}); +` + +const Personal_JS = ` +web3._extend({ + property: 'personal', + methods: + [ + new web3._extend.Method({ + name: 'importRawKey', + call: 'personal_importRawKey', + params: 2 + }) + ] +}); +` + +const TxPool_JS = `web3._extend({ property: 'txpool', methods: [ @@ -176,20 +324,6 @@ web3._extend({ }); ` -const Personal_JS = ` -web3._extend({ - property: 'personal', - methods: - [ - new web3._extend.Method({ - name: 'importRawKey', - call: 'personal_importRawKey', - params: 2 - }) - ] -}); -` - const Eth_JS = ` web3._extend({ property: 'eth', diff --git a/rpc/api/bzz.go b/rpc/api/bzz.go deleted file mode 100644 index c258879857..0000000000 --- a/rpc/api/bzz.go +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright 2015 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 Lesser 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with go-ethereum. If not, see . - -package api - -import ( - "encoding/json" - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/swarm" -) - -const ( - BzzApiVersion = "1.0" -) - -// eth api provider -// See https://github.com/ethereum/wiki/wiki/JSON-RPC -type bzzApi struct { - swarm *swarm.Swarm - methods map[string]bzzhandler - codec codec.ApiCoder -} - -// eth callback handler -type bzzhandler func(*bzzApi, *shared.Request) (interface{}, error) - -var ( - bzzMapping = map[string]bzzhandler{ - "bzz_info": (*bzzApi).Info, - "bzz_issue": (*bzzApi).Issue, - "bzz_cash": (*bzzApi).Cash, - "bzz_deposit": (*bzzApi).Deposit, - "bzz_register": (*bzzApi).Register, - "bzz_resolve": (*bzzApi).Resolve, - "bzz_download": (*bzzApi).Download, - "bzz_upload": (*bzzApi).Upload, - "bzz_get": (*bzzApi).Get, - "bzz_put": (*bzzApi).Put, - "bzz_modify": (*bzzApi).Modify, - } -) - -func newSwarmOfflineError(method string) error { - return shared.NewNotAvailableError(method, "swarm offline") -} - -// create new bzzApi instance -func NewBzzApi(stack *node.Node, codec codec.Codec) *bzzApi { - var swarm *swarm.Swarm - stack.Service(&swarm) - return &bzzApi{swarm, bzzMapping, codec.New(nil)} -} - -// collection with supported methods -func (self *bzzApi) Methods() []string { - methods := make([]string, len(self.methods)) - i := 0 - for k := range self.methods { - methods[i] = k - i++ - } - return methods -} - -// Execute given request -func (self *bzzApi) Execute(req *shared.Request) (interface{}, error) { - if callback, ok := self.methods[req.Method]; ok { - return callback(self, req) - } - - return nil, shared.NewNotImplementedError(req.Method) -} - -func (self *bzzApi) Name() string { - return shared.BzzApiName -} - -func (self *bzzApi) ApiVersion() string { - return BzzApiVersion -} - -func (self *bzzApi) Info(req *shared.Request) (interface{}, error) { - s := self.swarm - if s == nil { - return nil, newSwarmOfflineError(req.Method) - } - return s.Api().Info(), nil -} - -func (self *bzzApi) Issue(req *shared.Request) (interface{}, error) { - s := self.swarm - if s == nil { - return nil, newSwarmOfflineError(req.Method) - } - - args := new(BzzIssueArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - cheque, err := s.Api().Issue(common.HexToAddress(args.Beneficiary), args.Amount) - if err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - out, err := json.MarshalIndent(cheque, " ", "") - if err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - return string(out), nil -} - -func (self *bzzApi) Cash(req *shared.Request) (interface{}, error) { - s := self.swarm - if s == nil { - return nil, newSwarmOfflineError(req.Method) - } - - args := new(BzzCashArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - return s.Api().Cash(args.Cheque) - -} - -func (self *bzzApi) Deposit(req *shared.Request) (interface{}, error) { - s := self.swarm - if s == nil { - return nil, newSwarmOfflineError(req.Method) - } - - args := new(BzzDepositArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - return s.Api().Deposit(args.Amount) -} - -func (self *bzzApi) Register(req *shared.Request) (interface{}, error) { - s := self.swarm - if s == nil { - return nil, newSwarmOfflineError(req.Method) - } - - args := new(BzzRegisterArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - err := s.Api().Register(common.HexToAddress(args.Address), args.Domain, common.HexToHash(args.ContentHash)) - return err == nil, err -} - -func (self *bzzApi) Resolve(req *shared.Request) (interface{}, error) { - s := self.swarm - if s == nil { - return nil, newSwarmOfflineError(req.Method) - } - - args := new(BzzResolveArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - key, err := s.Api().Resolve(args.Domain) - return key.Hex(), err -} - -func (self *bzzApi) Download(req *shared.Request) (interface{}, error) { - - s := self.swarm - if s == nil { - return nil, newSwarmOfflineError(req.Method) - } - - args := new(BzzDownloadArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - err := s.Api().Download(args.BzzPath, args.LocalPath) - return err == nil, err -} - -func (self *bzzApi) Upload(req *shared.Request) (interface{}, error) { - - s := self.swarm - if s == nil { - return nil, newSwarmOfflineError(req.Method) - } - - args := new(BzzUploadArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - return s.Api().Upload(args.LocalPath, args.Index) -} - -func (self *bzzApi) Get(req *shared.Request) (interface{}, error) { - - s := self.swarm - if s == nil { - return nil, newSwarmOfflineError(req.Method) - } - - args := new(BzzGetArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - var content []byte - var mimeType string - var status, size int - var err error - content, mimeType, status, size, err = s.Api().Get(args.Path) - - obj := map[string]string{ - "content": string(content), - "contentType": mimeType, - "status": fmt.Sprintf("%v", status), - "size": fmt.Sprintf("%v", size), - } - - return obj, err -} - -func (self *bzzApi) Put(req *shared.Request) (interface{}, error) { - - s := self.swarm - if s == nil { - return nil, newSwarmOfflineError(req.Method) - } - - args := new(BzzPutArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - return s.Api().Put(args.Content, args.ContenType) -} - -func (self *bzzApi) Modify(req *shared.Request) (interface{}, error) { - - s := self.swarm - if s == nil { - return nil, newSwarmOfflineError(req.Method) - } - - args := new(BzzModifyArgs) - if err := self.codec.Decode(req.Params, &args); err != nil { - return nil, shared.NewDecodeParamError(err.Error()) - } - - return s.Api().Modify(args.RootHash, args.Path, args.ContentHash, args.ContentType) -} diff --git a/rpc/api/bzz_args.go b/rpc/api/bzz_args.go deleted file mode 100644 index 2aee255e15..0000000000 --- a/rpc/api/bzz_args.go +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright 2015 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 Lesser 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with go-ethereum. If not, see . - -package api - -import ( - "encoding/json" - "math/big" - - "github.com/ethereum/go-ethereum/common/chequebook" - "github.com/ethereum/go-ethereum/rpc/shared" -) - -type BzzDepositArgs struct { - Amount *big.Int -} - -func (args *BzzDepositArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - amount, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("Amount", "not a string") - } - args.Amount, ok = new(big.Int).SetString(amount, 10) - if !ok { - return shared.NewInvalidTypeError("Amount", "not a number") - } - - return nil -} - -type BzzCashArgs struct { - Cheque *chequebook.Cheque -} - -func (args *BzzCashArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - chequestr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("Cheque", "not a string") - } - var cheque chequebook.Cheque - err = json.Unmarshal([]byte(chequestr), &cheque) - if err != nil { - return shared.NewDecodeParamError(err.Error()) - } - args.Cheque = &cheque - - return nil -} - -type BzzIssueArgs struct { - Beneficiary string - Amount *big.Int -} - -func (args *BzzIssueArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 2) - } - - beneficiary, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("Amount", "not a string") - } - args.Beneficiary = beneficiary - - amount, ok := obj[1].(string) - if !ok { - return shared.NewInvalidTypeError("Amount", "not a string") - } - args.Amount, ok = new(big.Int).SetString(amount, 10) - if !ok { - return shared.NewInvalidTypeError("Amount", "not a number") - } - - return nil -} - -type BzzRegisterArgs struct { - Address, ContentHash, Domain string -} - -func (args *BzzRegisterArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 3 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("Address", "not a string") - } - args.Address = addstr - - addstr, ok = obj[1].(string) - if !ok { - return shared.NewInvalidTypeError("Domain", "not a string") - } - args.Domain = addstr - - addstr, ok = obj[2].(string) - if !ok { - return shared.NewInvalidTypeError("ContentHash", "not a string") - } - args.ContentHash = addstr - - return nil -} - -type BzzResolveArgs struct { - Domain string -} - -func (args *BzzResolveArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("Domain", "not a string") - } - args.Domain = addstr - - return nil -} - -type BzzDownloadArgs struct { - BzzPath, LocalPath string -} - -func (args *BzzDownloadArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("BzzPath", "not a string") - } - args.BzzPath = addstr - - addstr, ok = obj[1].(string) - if !ok { - return shared.NewInvalidTypeError("LocalPath", "not a string") - } - args.LocalPath = addstr - - return nil -} - -type BzzUploadArgs struct { - LocalPath, Index string -} - -func (args *BzzUploadArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("LocalPath", "not a string") - } - args.LocalPath = addstr - - if len(obj) > 1 { - addstr, ok := obj[1].(string) - if ok { - args.Index = addstr - } - } - - return nil -} - -type BzzGetArgs struct { - Path string -} - -func (args *BzzGetArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("Path", "not a string") - } - args.Path = addstr - - return nil -} - -type BzzPutArgs struct { - Content, ContenType string -} - -func (args *BzzPutArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 1 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("Content", "not a string") - } - args.Content = addstr - - addstr, ok = obj[1].(string) - if !ok { - return shared.NewInvalidTypeError("ContenType", "not a string") - } - args.ContenType = addstr - - return nil -} - -type BzzModifyArgs struct { - RootHash, Path, ContentHash, ContentType string -} - -func (args *BzzModifyArgs) UnmarshalJSON(b []byte) (err error) { - var obj []interface{} - if err := json.Unmarshal(b, &obj); err != nil { - return shared.NewDecodeParamError(err.Error()) - } - - if len(obj) < 2 { - return shared.NewInsufficientParamsError(len(obj), 1) - } - - addstr, ok := obj[0].(string) - if !ok { - return shared.NewInvalidTypeError("RootHash", "not a string") - } - args.RootHash = addstr - - addstr, ok = obj[1].(string) - if !ok { - return shared.NewInvalidTypeError("Path", "not a string") - } - args.Path = addstr - - if len(obj) >= 4 { - addstr, ok = obj[2].(string) - if ok { - args.ContentHash = addstr - } - - addstr, ok = obj[3].(string) - if ok { - args.ContentType = addstr - } - } - - return nil -} diff --git a/rpc/api/bzz_js.go b/rpc/api/bzz_js.go deleted file mode 100644 index 50bbd43d07..0000000000 --- a/rpc/api/bzz_js.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2015 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 Lesser 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 Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with go-ethereum. If not, see . - -package api - -const Bzz_JS = ` -web3._extend({ - property: 'bzz', - methods: - [ - new web3._extend.Method({ - name: 'deposit', - call: 'bzz_deposit', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'info', - call: 'bzz_info', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'cash', - call: 'bzz_cash', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'issue', - call: 'bzz_issue', - params: 2, - inputFormatter: [null, null] - }), - new web3._extend.Method({ - name: 'register', - call: 'bzz_register', - params: 3, - inputFormatter: [null, null, null] - }), - new web3._extend.Method({ - name: 'resolve', - call: 'bzz_resolve', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'download', - call: 'bzz_download', - params: 2, - inputFormatter: [null, null] - }), - new web3._extend.Method({ - name: 'upload', - call: 'bzz_upload', - params: 2, - inputFormatter: [null, null] - }), - new web3._extend.Method({ - name: 'get', - call: 'bzz_get', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'put', - call: 'bzz_put', - params: 2, - inputFormatter: [null, null] - }), - new web3._extend.Method({ - name: 'modify', - call: 'bzz_modify', - params: 4, - inputFormatter: [null, null, null, null] - }) - ], - properties: - [ - ] -}); -` diff --git a/rpc/api/utils.go b/rpc/api/utils.go deleted file mode 100644 index d52f6b3dc3..0000000000 --- a/rpc/api/utils.go +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "strings" - - "fmt" - - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc/codec" - "github.com/ethereum/go-ethereum/rpc/shared" - "github.com/ethereum/go-ethereum/xeth" -) - -var ( - // Mapping between the different methods each api supports - AutoCompletion = map[string][]string{ - "admin": []string{ - "addPeer", - "datadir", - "enableUserAgent", - "exportChain", - "getContractInfo", - "httpGet", - "importChain", - "nodeInfo", - "peers", - "register", - "registerUrl", - "saveInfo", - "setGlobalRegistrar", - "setHashReg", - "setUrlHint", - "setSolc", - "sleep", - "sleepBlocks", - "startNatSpec", - "startRPC", - "stopNatSpec", - "stopRPC", - "setGlobalRegistrar", - "setHashReg", - "setUrlHint", - "saveInfo", - "getContractInfo", - "sleep", - "httpGet", - "verbosity", - }, - "bzz": []string{ - "info", - "issue", - "cash", - "deposit", - "register", - "resolve", - "download", - "upload", - "get", - "put", - "modify", - }, - "db": []string{ - "getString", - "putString", - "getHex", - "putHex", - }, - "debug": []string{ - "dumpBlock", - "getBlockRlp", - "metrics", - "printBlock", - "processBlock", - "seedHash", - "setHead", - }, - "eth": []string{ - "accounts", - "blockNumber", - "call", - "contract", - "coinbase", - "compile.lll", - "compile.serpent", - "compile.solidity", - "contract", - "defaultAccount", - "defaultBlock", - "estimateGas", - "filter", - "getBalance", - "getBlock", - "getBlockTransactionCount", - "getBlockUncleCount", - "getCode", - "getNatSpec", - "getCompilers", - "gasPrice", - "getStorageAt", - "getTransaction", - "getTransactionCount", - "getTransactionFromBlock", - "getTransactionReceipt", - "getUncle", - "hashrate", - "mining", - "namereg", - "getPendingTransactions", - "resend", - "sendRawTransaction", - "sendTransaction", - "sign", - "syncing", - }, - "miner": []string{ - "hashrate", - "makeDAG", - "setEtherbase", - "setExtra", - "setGasPrice", - "startAutoDAG", - "setEtherbase", - "start", - "stopAutoDAG", - "stop", - }, - "net": []string{ - "peerCount", - "listening", - }, - "personal": []string{ - "listAccounts", - "newAccount", - "unlockAccount", - }, - "shh": []string{ - "post", - "newIdentity", - "hasIdentity", - "newGroup", - "addToGroup", - "filter", - }, - "txpool": []string{ - "status", - }, - "web3": []string{ - "sha3", - "version", - "fromWei", - "toWei", - "toHex", - "toAscii", - "fromAscii", - "toBigNumber", - "isAddress", - }, - } -) - -// Parse a comma separated API string to individual api's -func ParseApiString(apistr string, codec codec.Codec, xeth *xeth.XEth, stack *node.Node) ([]shared.EthereumApi, error) { - if len(strings.TrimSpace(apistr)) == 0 { - return nil, fmt.Errorf("Empty apistr provided") - } - - names := strings.Split(apistr, ",") - apis := make([]shared.EthereumApi, len(names)) - - var eth *eth.Ethereum - if stack != nil { - if err := stack.Service(ð); err != nil { - return nil, err - } - } - for i, name := range names { - switch strings.ToLower(strings.TrimSpace(name)) { - case shared.AdminApiName: - apis[i] = NewAdminApi(xeth, stack, codec) - case shared.BzzApiName: - apis[i] = NewBzzApi(stack, codec) - case shared.DebugApiName: - apis[i] = NewDebugApi(xeth, eth, codec) - case shared.DbApiName: - apis[i] = NewDbApi(xeth, eth, codec) - case shared.EthApiName: - apis[i] = NewEthApi(xeth, eth, codec) - case shared.MinerApiName: - apis[i] = NewMinerApi(eth, codec) - case shared.NetApiName: - apis[i] = NewNetApi(xeth, eth, codec) - case shared.ShhApiName: - apis[i] = NewShhApi(xeth, eth, codec) - case shared.TxPoolApiName: - apis[i] = NewTxPoolApi(xeth, eth, codec) - case shared.PersonalApiName: - apis[i] = NewPersonalApi(xeth, eth, codec) - case shared.Web3ApiName: - apis[i] = NewWeb3Api(xeth, codec) - case "rpc": // gives information about the RPC interface - continue - default: - return nil, fmt.Errorf("Unknown API '%s'", name) - } - } - return apis, nil -} - -func Javascript(name string) string { - switch strings.ToLower(strings.TrimSpace(name)) { - case shared.AdminApiName: - return Admin_JS - case shared.BzzApiName: - return Bzz_JS - case shared.DebugApiName: - return Debug_JS - case shared.DbApiName: - return Db_JS - case shared.EthApiName: - return Eth_JS - case shared.MinerApiName: - return Miner_JS - case shared.NetApiName: - return Net_JS - case shared.ShhApiName: - return Shh_JS - case shared.TxPoolApiName: - return TxPool_JS - case shared.PersonalApiName: - return Personal_JS - } - - return "" -} diff --git a/swarm/api/api.go b/swarm/api/api.go index 11bf4d81ec..883f226cf2 100644 --- a/swarm/api/api.go +++ b/swarm/api/api.go @@ -1,24 +1,16 @@ package api import ( - "bufio" "fmt" "io" - "math/big" - "net/http" - "os" - "path/filepath" "regexp" "strings" "sync" - "github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/chequebook" - "github.com/ethereum/go-ethereum/common/registrar" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/swarm/storage" ) var ( @@ -27,68 +19,91 @@ var ( domainAndVersion = regexp.MustCompile("[@:;,]+") ) +type Resolver interface { + Resolve(string) (storage.Key, error) +} + /* Api implements webserver/file system related content storage and retrieval on top of the dpa it is the public interface of the dpa which is included in the ethereum stack */ type Api struct { - dpa *storage.DPA - registrar registrar.VersionedRegistrar - conf *Config + dpa *storage.DPA + dns Resolver } //the api constructor initialises -func NewApi(dpa *storage.DPA, registrar registrar.VersionedRegistrar, conf *Config) (self *Api) { - return &Api{dpa, registrar, conf} -} - -// this should move over to chequebook ipc api -func (self *Api) Issue(beneficiary common.Address, amount *big.Int) (cheque *chequebook.Cheque, err error) { - return self.conf.Swap.Chequebook().Issue(beneficiary, amount) -} - -func (self *Api) Cash(cheque *chequebook.Cheque) (txhash string, err error) { - return self.conf.Swap.Chequebook().Cash(cheque) -} - -func (self *Api) Deposit(amount *big.Int) (txhash string, err error) { - return self.conf.Swap.Chequebook().Deposit(amount) -} - -// serialisable info about swarm -type Info struct { - *Config - *chequebook.Params -} - -func (self *Api) Info() *Info { - - return &Info{ - Config: self.conf, - Params: chequebook.ContractParams, - } - -} - -// Get uses iterative manifest retrieval and prefix matching -// to resolve path to content using dpa retrieve -func (self *Api) Get(bzzpath string) (content []byte, mimeType string, status int, size int, err error) { - var reader storage.SectionReader - reader, mimeType, status, err = self.getPath("/" + bzzpath) - if err != nil { - return - } - content = make([]byte, reader.Size()) - size, err = reader.Read(content) - if err == io.EOF { - err = nil +func NewApi(dpa *storage.DPA, dns Resolver) (self *Api) { + self = &Api{ + dpa: dpa, + dns: dns, } return } -// Put provides singleton manifest creation and optional name registration -// on top of dpa store +// DPA reader API +func (self *Api) Retrieve(key storage.Key) storage.SectionReader { + return self.dpa.Retrieve(key) +} + +func (self *Api) Store(data storage.SectionReader, wg *sync.WaitGroup) (key storage.Key, err error) { + return self.dpa.Store(data, wg) +} + +type ErrResolve error + +// DNS Resolver +func (self *Api) Resolve(hostPort string, nameresolver bool) (contentHash storage.Key, err error) { + if hashMatcher.MatchString(hostPort) || self.dns == nil { + glog.V(logger.Detail).Infof("[BZZ] host is a contentHash: '%v'", hostPort) + return storage.Key(common.Hex2Bytes(hostPort)), nil + } + if !nameresolver { + err = fmt.Errorf("'%s' is not a content hash value.", hostPort) + return + } + contentHash, err = self.dns.Resolve(hostPort) + if err != nil { + err = ErrResolve(err) + glog.V(logger.Warn).Infof("[BZZ] DNS error : %v", err) + } + glog.V(logger.Detail).Infof("[BZZ] host lookup: %v -> %v", err) + return +} + +func parse(uri string) (hostPort, path string) { + parts := slashes.Split(uri, 3) + var i int + if len(parts) == 0 { + return + } + // beginning with slash is now optional + for len(parts[i]) == 0 { + i++ + } + hostPort = parts[i] + for i < len(parts)-1 { + i++ + if len(path) > 0 { + path = path + "/" + parts[i] + } else { + path = parts[i] + } + } + glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path) + return +} + +func (self *Api) parseAndResolve(uri string, nameresolver bool) (contentHash storage.Key, hostPort, path string, err error) { + hostPort, path = parse(uri) + //resolving host and port + contentHash, err = self.Resolve(hostPort, nameresolver) + glog.V(logger.Debug).Infof("[BZZ] Resolved '%s' to contentHash: '%s', path: '%s'", uri, contentHash, path) + return +} + +// Put provides singleton manifest creation on top of dpa store func (self *Api) Put(content, contentType string) (string, error) { sr := io.NewSectionReader(strings.NewReader(content), 0, int64(len(content))) wg := &sync.WaitGroup{} @@ -106,8 +121,36 @@ func (self *Api) Put(content, contentType string) (string, error) { return key.String(), nil } -func (self *Api) Modify(rootHash, path, contentHash, contentType string) (newRootHash string, err error) { - root := common.Hex2Bytes(rootHash) +// Get uses iterative manifest retrieval and prefix matching +// to resolve path to content using dpa retrieve +// it returns a section reader, mimeType, status and an error +func (self *Api) Get(uri string, nameresolver bool) (reader storage.SectionReader, mimeType string, status int, err error) { + + key, _, path, err := self.parseAndResolve(uri, nameresolver) + + trie, err := loadManifest(self.dpa, key) + if err != nil { + glog.V(logger.Warn).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err) + return + } + + glog.V(logger.Detail).Infof("[BZZ] Swarm: getEntry(%s)", path) + entry, _ := trie.getEntry(path) + if entry != nil { + key = common.Hex2Bytes(entry.Hash) + status = entry.Status + mimeType = entry.ContentType + glog.V(logger.Detail).Infof("[BZZ] Swarm: content lookup key: '%v' (%v)", key, mimeType) + reader = self.dpa.Retrieve(key) + } else { + err = fmt.Errorf("manifest entry for '%s' not found", path) + glog.V(logger.Warn).Infof("[BZZ] Swarm: %v", err) + } + return +} + +func (self *Api) Modify(uri, contentHash, contentType string, nameresolver bool) (newRootHash string, err error) { + root, _, path, err := self.parseAndResolve(uri, nameresolver) trie, err := loadManifest(self.dpa, root) if err != nil { return @@ -130,327 +173,3 @@ func (self *Api) Modify(rootHash, path, contentHash, contentType string) (newRoo } return trie.hash.String(), nil } - -const maxParallelFiles = 5 - -// Download replicates the manifest path structure on the local filesystem -// under localpath -func (self *Api) Download(bzzpath, localpath string) (err error) { - lpath, err := filepath.Abs(filepath.Clean(localpath)) - if err != nil { - return - } - err = os.MkdirAll(lpath, os.ModePerm) - if err != nil { - return - } - - parts := slashes.Split(bzzpath, 3) - if len(parts) < 2 { - return fmt.Errorf("Invalid bzz path") - } - hostPort := parts[1] - var path string - if len(parts) > 2 { - path = regularSlashes(parts[2]) + "/" - } - glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path) - - //resolving host and port - var key storage.Key - key, err = self.Resolve(hostPort) - if err != nil { - err = errResolve(err) - glog.V(logger.Debug).Infof("[BZZ] Swarm: error : %v", err) - return - } - - trie, err := loadManifest(self.dpa, key) - if err != nil { - glog.V(logger.Debug).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err) - return - } - - type downloadListEntry struct { - key storage.Key - path string - } - - var list []*downloadListEntry - var mde, mderr error - - prevPath := lpath - err = trie.listWithPrefix(path, func(entry *manifestTrieEntry, suffix string) { // TODO: paralellize - key := common.Hex2Bytes(entry.Hash) - path := lpath + "/" + suffix - dir := filepath.Dir(path) - if dir != prevPath { - mde = os.MkdirAll(dir, os.ModePerm) - if mde != nil { - mderr = mde - } - prevPath = dir - } - if (mde == nil) && (path != dir+"/") { - list = append(list, &downloadListEntry{key: key, path: path}) - } - }) - if err == nil { - err = mderr - } - - cnt := len(list) - errors := make([]error, cnt) - done := make(chan bool, maxParallelFiles) - dcnt := 0 - - for i, entry := range list { - if i >= dcnt+maxParallelFiles { - <-done - dcnt++ - } - go func(i int, entry *downloadListEntry, done chan bool) { - f, err := os.Create(entry.path) // TODO: path separators - if err == nil { - reader := self.dpa.Retrieve(entry.key) - writer := bufio.NewWriter(f) - _, err = io.CopyN(writer, reader, reader.Size()) // TODO: handle errors - err2 := writer.Flush() - if err == nil { - err = err2 - } - err2 = f.Close() - if err == nil { - err = err2 - } - } - - errors[i] = err - done <- true - }(i, entry, done) - } - for dcnt < cnt { - <-done - dcnt++ - } - - if err != nil { - return - } - for i, _ := range list { - if errors[i] != nil { - return errors[i] - } - } - return -} - -// Upload replicates a local directory as a manifest file and uploads it -// using dpa store -// TODO: localpath should point to a manifest -func (self *Api) Upload(lpath, index string) (string, error) { - var list []*manifestTrieEntry - localpath, err := filepath.Abs(filepath.Clean(lpath)) - if err != nil { - return "", err - } - - f, err := os.Open(localpath) - if err != nil { - return "", err - } - stat, err := f.Stat() - if err != nil { - return "", err - } - - var start int - if stat.IsDir() { - start = len(localpath) - glog.V(logger.Debug).Infof("[BZZ] uploading '%s'", localpath) - err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error { - if (err == nil) && !info.IsDir() { - //fmt.Printf("lp %s path %s\n", localpath, path) - if len(path) <= start { - return fmt.Errorf("Path is too short") - } - if path[:start] != localpath { - return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath) - } - entry := &manifestTrieEntry{ - Path: path, - } - list = append(list, entry) - } - return err - }) - if err != nil { - return "", err - } - } else { - dir := filepath.Dir(localpath) - start = len(dir) - if len(localpath) <= start { - return "", fmt.Errorf("Path is too short") - } - if localpath[:start] != dir { - return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir) - } - entry := &manifestTrieEntry{ - Path: localpath, - } - list = append(list, entry) - } - - cnt := len(list) - errors := make([]error, cnt) - done := make(chan bool, maxParallelFiles) - dcnt := 0 - - for i, entry := range list { - if i >= dcnt+maxParallelFiles { - <-done - dcnt++ - } - go func(i int, entry *manifestTrieEntry, done chan bool) { - f, err := os.Open(entry.Path) - if err == nil { - stat, _ := f.Stat() - sr := io.NewSectionReader(f, 0, stat.Size()) - wg := &sync.WaitGroup{} - var hash storage.Key - hash, err = self.dpa.Store(sr, wg) - if hash != nil { - list[i].Hash = hash.String() - } - wg.Wait() - if err == nil { - first512 := make([]byte, 512) - fread, _ := sr.ReadAt(first512, 0) - if fread > 0 { - mimeType := http.DetectContentType(first512[:fread]) - if filepath.Ext(entry.Path) == ".css" { - mimeType = "text/css" - } - list[i].ContentType = mimeType - //fmt.Printf("%v %v %v\n", entry.Path, mimeType, filepath.Ext(entry.Path)) - } - } - f.Close() - } - errors[i] = err - done <- true - }(i, entry, done) - } - for dcnt < cnt { - <-done - dcnt++ - } - - trie := &manifestTrie{ - dpa: self.dpa, - } - for i, entry := range list { - if errors[i] != nil { - return "", errors[i] - } - entry.Path = regularSlashes(entry.Path[start:]) - if entry.Path == index { - ientry := &manifestTrieEntry{ - Path: "", - Hash: entry.Hash, - ContentType: entry.ContentType, - } - trie.addEntry(ientry) - } - trie.addEntry(entry) - } - - err2 := trie.recalcAndStore() - var hs string - if err2 == nil { - hs = trie.hash.String() - } - return hs, err2 -} - -func (self *Api) Register(sender common.Address, domain string, hash common.Hash) (err error) { - domainhash := common.BytesToHash(crypto.Sha3([]byte(domain))) - - if self.registrar != nil { - glog.V(logger.Debug).Infof("[BZZ] Swarm: host '%s' (hash: '%v') to be registered as '%v'", domain, domainhash.Hex(), hash.Hex()) - _, err = self.registrar.Registry().SetHashToHash(sender, domainhash, hash) - } else { - err = fmt.Errorf("no registry: %v", err) - } - return -} - -type errResolve error - -func (self *Api) Resolve(hostPort string) (contentHash storage.Key, err error) { - host := hostPort - if hashMatcher.MatchString(host) { - contentHash = storage.Key(common.Hex2Bytes(host)) - glog.V(logger.Debug).Infof("[BZZ] Swarm: host is a contentHash: '%v'", contentHash) - } else { - if self.registrar != nil { - var hash common.Hash - var version *big.Int - parts := domainAndVersion.Split(host, 3) - if len(parts) > 1 && parts[1] != "" { - host = parts[0] - version = common.Big(parts[1]) - } - hostHash := crypto.Sha3Hash([]byte(host)) - hash, err = self.registrar.Resolver(version).HashToHash(hostHash) - if err != nil { - err = fmt.Errorf("unable to resolve '%s': %v", hostPort, err) - } - contentHash = storage.Key(hash.Bytes()) - glog.V(logger.Debug).Infof("[BZZ] Swarm: resolve host '%s' to contentHash: '%v'", hostPort, contentHash) - } else { - err = fmt.Errorf("no resolver '%s': %v", hostPort, err) - } - } - return -} - -func (self *Api) getPath(uri string) (reader storage.SectionReader, mimeType string, status int, err error) { - parts := slashes.Split(uri, 3) - hostPort := parts[1] - var path string - if len(parts) > 2 { - path = parts[2] - } - glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path) - - //resolving host and port - var key storage.Key - key, err = self.Resolve(hostPort) - if err != nil { - err = errResolve(err) - glog.V(logger.Debug).Infof("[BZZ] Swarm: error : %v", err) - return - } - - trie, err := loadManifest(self.dpa, key) - if err != nil { - glog.V(logger.Debug).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err) - return - } - - glog.V(logger.Debug).Infof("[BZZ] Swarm: getEntry(%s)", path) - entry, _ := trie.getEntry(path) - if entry != nil { - key = common.Hex2Bytes(entry.Hash) - status = entry.Status - mimeType = entry.ContentType - glog.V(logger.Debug).Infof("[BZZ] Swarm: content lookup key: '%v' (%v)", key, mimeType) - reader = self.dpa.Retrieve(key) - } else { - err = fmt.Errorf("manifest entry for '%s' not found", path) - glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err) - } - return -} diff --git a/swarm/api/api_test.go b/swarm/api/api_test.go index 81156afcd8..0ee3ca8df7 100644 --- a/swarm/api/api_test.go +++ b/swarm/api/api_test.go @@ -1,205 +1,86 @@ package api import ( - "bytes" + // "bytes" "io/ioutil" "os" - "path" - "runtime" "testing" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/swarm/storage" ) -//TODO: add tests for resolver/registrar -// will most likely be its own package under service? - -var ( - testDir string -) - -func init() { - _, filename, _, _ := runtime.Caller(1) - testDir = path.Join(path.Dir(filename), "../test") -} - -func testApi() (api *Api, err error) { +func testApi(t *testing.T, f func(*Api)) { datadir, err := ioutil.TempDir("", "bzz-test") if err != nil { - return nil, err + t.Fatalf("unable to create temp dir: %v", err) } os.RemoveAll(datadir) + defer os.RemoveAll(datadir) dpa, err := storage.NewLocalDPA(datadir) if err != nil { return } - prvkey, _ := crypto.GenerateKey() + api := NewApi(dpa, nil) + dpa.Start() + f(api) + dpa.Stop() +} - config, err := NewConfig(datadir, common.Address{}, prvkey) - if err != nil { - return +type testResponse struct { + reader storage.SectionReader + *Response +} + +func checkResponse(t *testing.T, resp *testResponse, exp *Response) { + + if resp.MimeType != exp.MimeType { + t.Errorf("incorrect mimeType. expected '%s', got '%s'", exp.MimeType, resp.MimeType) } - api = NewApi(dpa, nil, config) - api.dpa.Start() + if resp.Status != exp.Status { + t.Errorf("incorrect status. expected '%d', got '%d'", exp.Status, resp.Status) + } + if resp.Size != exp.Size { + t.Errorf("incorrect size. expected '%d', got '%d'", exp.Size, resp.Size) + } + if resp.reader != nil { + content := make([]byte, resp.Size) + read, _ := resp.reader.Read(content) + if int64(read) != exp.Size { + t.Errorf("incorrect content length. expected '%s...', got '%s...'", read, exp.Size) + } + resp.Content = string(content) + } + if resp.Content != exp.Content { + // if !bytes.Equal(resp.Content, exp.Content) { + t.Errorf("incorrect content. expected '%s...', got '%s...'", string(exp.Content), string(resp.Content)) + } +} - return +// func expResponse(content []byte, mimeType string, status int) *Response { +func expResponse(content string, mimeType string, status int) *Response { + return &Response{mimeType, status, int64(len(content)), content} +} + +// func testGet(t *testing.T, api *Api, bzzhash string) *testResponse { +func testGet(t *testing.T, api *Api, bzzhash string) *testResponse { + reader, mimeType, status, err := api.Get(bzzhash, true) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + return &testResponse{reader, &Response{mimeType, status, reader.Size(), ""}} + // return &testResponse{reader, &Response{mimeType, status, reader.Size(), nil}} } func TestApiPut(t *testing.T) { - api, err := testApi() - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - defer api.dpa.Stop() - expContent := "hello" - expMimeType := "text/plain" - expStatus := 0 - expSize := len(expContent) - bzzhash, err := api.Put(expContent, expMimeType) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - testGet(t, api, bzzhash, []byte(expContent), expMimeType, expStatus, expSize) -} - -func testGet(t *testing.T, api *Api, bzzhash string, expContent []byte, expMimeType string, expStatus int, expSize int) { - content, mimeType, status, size, err := api.Get(bzzhash) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - if !bytes.Equal(content, expContent) { - t.Errorf("incorrect content. expected '%s...', got '%s...'", string(expContent), string(content)) - } - if mimeType != expMimeType { - t.Errorf("incorrect mimeType. expected '%s', got '%s'", expMimeType, mimeType) - } - if status != expStatus { - t.Errorf("incorrect status. expected '%d', got '%d'", expStatus, status) - } - if size != expSize { - t.Errorf("incorrect size. expected '%d', got '%d'", expSize, size) - } -} - -func TestApiDirUpload(t *testing.T) { - t.Skip("FIXME") - api, err := testApi() - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - bzzhash, err := api.Upload(path.Join(testDir, "test0"), "") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html")) - testGet(t, api, path.Join(bzzhash, "index.html"), content, "text/html; charset=utf-8", 0, 202) - - content, err = ioutil.ReadFile(path.Join(testDir, "test0", "index.css")) - testGet(t, api, path.Join(bzzhash, "index.css"), content, "text/css", 0, 132) - - content, err = ioutil.ReadFile(path.Join(testDir, "test0", "img", "logo.png")) - testGet(t, api, path.Join(bzzhash, "img", "logo.png"), content, "image/png", 0, 18136) - - _, _, _, _, err = api.Get(bzzhash) - if err == nil { - t.Errorf("expected error: %v", err) - } -} - -func TestApiDirUploadModify(t *testing.T) { - t.Skip("FIXME") - api, err := testApi() - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - bzzhash, err := api.Upload(path.Join(testDir, "test0"), "") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - bzzhash, err = api.Modify(bzzhash, "index.html", "", "") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - bzzhash, err = api.Modify(bzzhash, "index2.html", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - bzzhash, err = api.Modify(bzzhash, "img/logo.png", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html")) - testGet(t, api, path.Join(bzzhash, "index2.html"), content, "text/html; charset=utf-8", 0, 202) - testGet(t, api, path.Join(bzzhash, "img", "logo.png"), content, "text/html; charset=utf-8", 0, 202) - - content, err = ioutil.ReadFile(path.Join(testDir, "test0", "index.css")) - testGet(t, api, path.Join(bzzhash, "index.css"), content, "text/css", 0, 132) - - _, _, _, _, err = api.Get(bzzhash) - if err == nil { - t.Errorf("expected error: %v", err) - } -} - -func TestApiDirUploadWithRootFile(t *testing.T) { - api, err := testApi() - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - bzzhash, err := api.Upload(path.Join(testDir, "test0"), "index.html") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html")) - testGet(t, api, bzzhash, content, "text/html; charset=utf-8", 0, 202) -} - -func TestApiFileUpload(t *testing.T) { - api, err := testApi() - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - bzzhash, err := api.Upload(path.Join(testDir, "test0", "index.html"), "") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html")) - testGet(t, api, path.Join(bzzhash, "index.html"), content, "text/html; charset=utf-8", 0, 202) -} - -func TestApiFileUploadWithRootFile(t *testing.T) { - api, err := testApi() - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - bzzhash, err := api.Upload(path.Join(testDir, "test0", "index.html"), "index.html") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - content, err := ioutil.ReadFile(path.Join(testDir, "test0", "index.html")) - testGet(t, api, bzzhash, content, "text/html; charset=utf-8", 0, 202) + testApi(t, func(api *Api) { + content := "hello" + exp := expResponse(content, "text/plain", 0) + // exp := expResponse([]byte(content), "text/plain", 0) + bzzhash, err := api.Put(content, exp.MimeType) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + resp := testGet(t, api, bzzhash) + checkResponse(t, resp, exp) + }) } diff --git a/swarm/api/config_test.go b/swarm/api/config_test.go index 44bb484667..9bed72d23d 100644 --- a/swarm/api/config_test.go +++ b/swarm/api/config_test.go @@ -91,6 +91,7 @@ func TestConfigWriteRead(t *testing.T) { t.Fatalf("default config file cannot be read: %v", err) } exp := strings.Replace(defaultConfig, "TMPDIR", tmp, -1) + exp = strings.Replace(exp, "\\", "\\\\", -1) if string(data) != exp { t.Fatalf("default config mismatch:\nexpected:\n'%v'\ngot:\n'%v'", exp, string(data)) diff --git a/swarm/api/ethereum.go b/swarm/api/ethereum.go deleted file mode 100644 index 1027d1e768..0000000000 --- a/swarm/api/ethereum.go +++ /dev/null @@ -1,320 +0,0 @@ -package api - -import ( - "errors" - "fmt" - "math/big" - "regexp" - "sync" - - "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/registrar" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" -) - -/* -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! THIS IS CURRENTLY A PLACEHOLEDER (HACK) UNTIL PRC v2 -!! https://github.com/ethereum/go-ethereum/pull/1912 is merged -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -*/ - -var ( - defaultGasPrice = big.NewInt(10000000000000) //150000000000 - defaultGas = big.NewInt(90000) //500000 - addrReg = regexp.MustCompile(`^(0x)?[a-fA-F0-9]{40}$`) -) - -type ethApi struct { - eth *eth.Ethereum - gpo *eth.GasPriceOracle - transactionMu sync.RWMutex - transactMu sync.RWMutex - state *state.StateDB -} - -func NewEthApi(ethereum *eth.Ethereum) *ethApi { - return ðApi{ - eth: ethereum, - gpo: eth.NewGasPriceOracle(ethereum), - } -} - -// subscribes to new head block events and -// waits until blockchain height is greater n at any time -// given the current head, waits for the next chain event -// sets the state to the current head -// loop is async and quit by closing the channel -// used in tests and JS console debug module to control advancing private chain manually -// Note: this is not threadsafe, only called in JS single process and tests -func (self *ethApi) UpdateState() (wait chan *big.Int) { - wait = make(chan *big.Int) - self.state, _ = state.New(self.eth.BlockChain().GetBlockByNumber(0).Root(), self.eth.ChainDb()) - - go func() { - eventSub := self.eth.EventMux().Subscribe(core.ChainHeadEvent{}) - defer eventSub.Unsubscribe() - - var m, n *big.Int - var ok bool - - eventCh := eventSub.Chan() - for { - select { - case event, ok := <-eventCh: - if !ok { - // Event subscription closed, set the channel to nil to stop spinning - eventCh = nil - continue - } - // A real event arrived, process if new head block assignment - if event, ok := event.Data.(core.ChainHeadEvent); ok { - m = event.Block.Number() - if n != nil && n.Cmp(m) < 0 { - wait <- n - n = nil - } - statedb, err := state.New(event.Block.Root(), self.eth.ChainDb()) - if err != nil { - glog.V(logger.Error).Infoln("Could not create new state: %v", err) - return - } - self.state = statedb - } - case n, ok = <-wait: - if !ok { - return - } - } - } - }() - return -} - -func (self *ethApi) AtStateNum(num int64) registrar.Backend { - var st *state.StateDB - var err error - switch num { - case -2: - st = self.eth.Miner().PendingState().Copy() - default: - if block := self.getBlockByHeight(num); block != nil { - st, err = state.New(block.Root(), self.eth.ChainDb()) - if err != nil { - return nil - } - } else { - st, err = state.New(self.eth.BlockChain().GetBlockByNumber(0).Root(), self.eth.ChainDb()) - if err != nil { - return nil - } - } - } - return registrar.Backend(ðApi{ - eth: self.eth, - state: st, - }) -} -func (self *ethApi) GetTxReceipt(txhash common.Hash) *types.Receipt { - return core.GetReceipt(self.eth.ChainDb(), txhash) -} - -func (self *ethApi) StorageAt(addr, storageAddr string) string { - return self.state.GetState(common.HexToAddress(addr), common.HexToHash(storageAddr)).Hex() -} - -func (self *ethApi) CodeAt(address string) string { - return common.ToHex(self.state.GetCode(common.HexToAddress(address))) -} - -func (self *ethApi) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr string) (string, string, error) { - statedb := self.state.Copy() - var from *state.StateObject - if len(fromStr) == 0 { - accounts, err := self.eth.AccountManager().Accounts() - if err != nil || len(accounts) == 0 { - from = statedb.GetOrNewStateObject(common.Address{}) - } else { - from = statedb.GetOrNewStateObject(accounts[0].Address) - } - } else { - from = statedb.GetOrNewStateObject(common.HexToAddress(fromStr)) - } - - from.SetBalance(common.MaxBig) - - msg := callmsg{ - from: from, - gas: common.Big(gasStr), - gasPrice: common.Big(gasPriceStr), - value: common.Big(valueStr), - data: common.FromHex(dataStr), - } - if len(toStr) > 0 { - addr := common.HexToAddress(toStr) - msg.to = &addr - } - - if msg.gas.Cmp(big.NewInt(0)) == 0 { - msg.gas = big.NewInt(50000000) - } - - if msg.gasPrice.Cmp(big.NewInt(0)) == 0 { - msg.gasPrice = self.DefaultGasPrice() - } - - header := self.CurrentBlock().Header() - vmenv := core.NewEnv(statedb, self.eth.BlockChain(), msg, header) - gp := new(core.GasPool).AddGas(common.MaxBig) - res, gas, err := core.ApplyMessage(vmenv, msg, gp) - return common.ToHex(res), gas.String(), err -} - -func (self *ethApi) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { - - if len(toStr) > 0 && toStr != "0x" && !isAddress(toStr) { - return "", errors.New("Invalid address") - } - - var ( - from = common.HexToAddress(fromStr) - to = common.HexToAddress(toStr) - value = common.Big(valueStr) - gas *big.Int - price *big.Int - data []byte - contractCreation bool - ) - - if len(gasStr) == 0 { - gas = DefaultGas() - } else { - gas = common.Big(gasStr) - } - - if len(gasPriceStr) == 0 { - price = self.DefaultGasPrice() - } else { - price = common.Big(gasPriceStr) - } - - data = common.FromHex(codeStr) - if len(toStr) == 0 { - contractCreation = true - } - - self.transactMu.Lock() - defer self.transactMu.Unlock() - - var nonce uint64 - if len(nonceStr) != 0 { - nonce = common.Big(nonceStr).Uint64() - } else { - state := self.eth.TxPool().State() - nonce = state.GetNonce(from) - } - var tx *types.Transaction - if contractCreation { - tx = types.NewContractCreation(nonce, value, gas, price, data) - } else { - tx = types.NewTransaction(nonce, to, value, gas, price, data) - } - - signed, err := self.sign(tx, from, false) - if err != nil { - return "", err - } - if err = self.eth.TxPool().Add(signed); err != nil { - return "", err - } - - if contractCreation { - addr := crypto.CreateAddress(from, nonce) - glog.V(logger.Info).Infof("Tx(%s) created: %s\n", signed.Hash().Hex(), addr.Hex()) - } else { - glog.V(logger.Info).Infof("Tx(%s) to: %s\n", signed.Hash().Hex(), tx.To().Hex()) - } - - return signed.Hash().Hex(), nil -} - -func (self *ethApi) sign(tx *types.Transaction, from common.Address, didUnlock bool) (*types.Transaction, error) { - hash := tx.SigHash() - sig, err := self.doSign(from, hash, didUnlock) - if err != nil { - return tx, err - } - return tx.WithSignature(sig) -} - -func (self *ethApi) doSign(from common.Address, hash common.Hash, didUnlock bool) ([]byte, error) { - sig, err := self.eth.AccountManager().Sign(accounts.Account{Address: from}, hash.Bytes()) - if err == accounts.ErrLocked { - if didUnlock { - return nil, fmt.Errorf("signer account still locked after successful unlock") - } - // retry signing, the account should now be unlocked. - return self.doSign(from, hash, true) - } else if err != nil { - return nil, err - } - return sig, nil -} - -func DefaultGas() *big.Int { return new(big.Int).Set(defaultGas) } - -func (self *ethApi) DefaultGasPrice() *big.Int { - return self.gpo.SuggestPrice() -} - -func (self *ethApi) CurrentBlock() *types.Block { - return self.eth.BlockChain().CurrentBlock() -} - -func (self *ethApi) getBlockByHeight(height int64) *types.Block { - var num uint64 - - switch height { - case -2: - return self.eth.Miner().PendingBlock() - case -1: - return self.CurrentBlock() - default: - if height < 0 { - return nil - } - - num = uint64(height) - } - - return self.eth.BlockChain().GetBlockByNumber(num) -} - -// callmsg is the message type used for call transations. -type callmsg struct { - from *state.StateObject - to *common.Address - gas, gasPrice *big.Int - value *big.Int - data []byte -} - -func isAddress(addr string) bool { - return addrReg.MatchString(addr) -} - -// accessor boilerplate to implement core.Message -func (m callmsg) From() (common.Address, error) { return m.from.Address(), nil } -func (m callmsg) Nonce() uint64 { return m.from.Nonce() } -func (m callmsg) To() *common.Address { return m.to } -func (m callmsg) GasPrice() *big.Int { return m.gasPrice } -func (m callmsg) Gas() *big.Int { return m.gas } -func (m callmsg) Value() *big.Int { return m.value } -func (m callmsg) Data() []byte { return m.data } diff --git a/swarm/api/filesystem.go b/swarm/api/filesystem.go new file mode 100644 index 0000000000..002bb95273 --- /dev/null +++ b/swarm/api/filesystem.go @@ -0,0 +1,257 @@ +package api + +import ( + "bufio" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/swarm/storage" +) + +const maxParallelFiles = 5 + +type FileSystem struct { + api *Api +} + +func NewFileSystem(api *Api) *FileSystem { + return &FileSystem{api} +} + +// Upload replicates a local directory as a manifest file and uploads it +// using dpa store +// TODO: localpath should point to a manifest +func (self *FileSystem) Upload(lpath, index string) (string, error) { + var list []*manifestTrieEntry + localpath, err := filepath.Abs(filepath.Clean(lpath)) + if err != nil { + return "", err + } + + f, err := os.Open(localpath) + if err != nil { + return "", err + } + stat, err := f.Stat() + if err != nil { + return "", err + } + + var start int + if stat.IsDir() { + start = len(localpath) + glog.V(logger.Debug).Infof("[BZZ] uploading '%s'", localpath) + err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error { + if (err == nil) && !info.IsDir() { + //fmt.Printf("lp %s path %s\n", localpath, path) + if len(path) <= start { + return fmt.Errorf("Path is too short") + } + if path[:start] != localpath { + return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath) + } + entry := &manifestTrieEntry{ + Path: path, + } + list = append(list, entry) + } + return err + }) + if err != nil { + return "", err + } + } else { + dir := filepath.Dir(localpath) + start = len(dir) + if len(localpath) <= start { + return "", fmt.Errorf("Path is too short") + } + if localpath[:start] != dir { + return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir) + } + entry := &manifestTrieEntry{ + Path: localpath, + } + list = append(list, entry) + } + + cnt := len(list) + errors := make([]error, cnt) + done := make(chan bool, maxParallelFiles) + dcnt := 0 + + for i, entry := range list { + if i >= dcnt+maxParallelFiles { + <-done + dcnt++ + } + go func(i int, entry *manifestTrieEntry, done chan bool) { + f, err := os.Open(entry.Path) + if err == nil { + stat, _ := f.Stat() + sr := io.NewSectionReader(f, 0, stat.Size()) + wg := &sync.WaitGroup{} + var hash storage.Key + hash, err = self.api.dpa.Store(sr, wg) + if hash != nil { + list[i].Hash = hash.String() + } + wg.Wait() + if err == nil { + first512 := make([]byte, 512) + fread, _ := sr.ReadAt(first512, 0) + if fread > 0 { + mimeType := http.DetectContentType(first512[:fread]) + if filepath.Ext(entry.Path) == ".css" { + mimeType = "text/css" + } + list[i].ContentType = mimeType + } + } + f.Close() + } + errors[i] = err + done <- true + }(i, entry, done) + } + for dcnt < cnt { + <-done + dcnt++ + } + + trie := &manifestTrie{ + dpa: self.api.dpa, + } + for i, entry := range list { + if errors[i] != nil { + return "", errors[i] + } + entry.Path = RegularSlashes(entry.Path[start:]) + if entry.Path == index { + ientry := &manifestTrieEntry{ + Path: "", + Hash: entry.Hash, + ContentType: entry.ContentType, + } + trie.addEntry(ientry) + } + trie.addEntry(entry) + } + + err2 := trie.recalcAndStore() + var hs string + if err2 == nil { + hs = trie.hash.String() + } + return hs, err2 +} + +// Download replicates the manifest path structure on the local filesystem +// under localpath +func (self *FileSystem) Download(bzzpath, localpath string) error { + lpath, err := filepath.Abs(filepath.Clean(localpath)) + if err != nil { + return err + } + err = os.MkdirAll(lpath, os.ModePerm) + if err != nil { + return err + } + + //resolving host and port + key, _, path, err := self.api.parseAndResolve(bzzpath, true) + if err != nil { + return err + } + // if len(path) > 0 { + // path += "/" + // } + + trie, err := loadManifest(self.api.dpa, key) + if err != nil { + glog.V(logger.Warn).Infof("[BZZ] fs.Download: loadManifestTrie error: %v", err) + return err + } + + type downloadListEntry struct { + key storage.Key + path string + } + + var list []*downloadListEntry + var mde, mderr error + + prevPath := lpath + err = trie.listWithPrefix(path, func(entry *manifestTrieEntry, suffix string) { // TODO: paralellize + glog.V(logger.Detail).Infof("[BZZ] fs.Download: %#v", entry) + + key := common.Hex2Bytes(entry.Hash) + path := lpath + "/" + suffix + dir := filepath.Dir(path) + if dir != prevPath { + mde = os.MkdirAll(dir, os.ModePerm) + if mde != nil { + mderr = mde + } + prevPath = dir + } + if (mde == nil) && (path != dir+"/") { + list = append(list, &downloadListEntry{key: key, path: path}) + } + }) + if err == nil { + err = mderr + } + + cnt := len(list) + errors := make([]error, cnt) + done := make(chan bool, maxParallelFiles) + dcnt := 0 + + for i, entry := range list { + if i >= dcnt+maxParallelFiles { + <-done + dcnt++ + } + go func(i int, entry *downloadListEntry, done chan bool) { + f, err := os.Create(entry.path) // TODO: path separators + if err == nil { + reader := self.api.dpa.Retrieve(entry.key) + writer := bufio.NewWriter(f) + _, err = io.CopyN(writer, reader, reader.Size()) // TODO: handle errors + err2 := writer.Flush() + if err == nil { + err = err2 + } + err2 = f.Close() + if err == nil { + err = err2 + } + } + + errors[i] = err + done <- true + }(i, entry, done) + } + for dcnt < cnt { + <-done + dcnt++ + } + + if err != nil { + return err + } + for i, _ := range list { + if errors[i] != nil { + return errors[i] + } + } + return err +} diff --git a/swarm/api/filesystem_test.go b/swarm/api/filesystem_test.go new file mode 100644 index 0000000000..e0bb8915a9 --- /dev/null +++ b/swarm/api/filesystem_test.go @@ -0,0 +1,176 @@ +package api + +import ( + "io/ioutil" + "os" + "path" + "runtime" + "testing" +) + +var ( + testDir string + testDownloadDir string +) + +func init() { + _, filename, _, _ := runtime.Caller(1) + testDir = path.Join(path.Dir(filename), "../test") + testDownloadDir, _ = ioutil.TempDir(os.TempDir(), "bzz-test") +} + +func testFileSystem(t *testing.T, f func(*FileSystem)) { + testApi(t, func(api *Api) { + f(NewFileSystem(api)) + }) +} + +func readPath(t *testing.T, parts ...string) string { + // func readPath(t *testing.T, parts ...string) []byte { + file := path.Join(parts...) + content, err := ioutil.ReadFile(file) + if err != nil { + t.Fatalf("unexpected error reading '%v': %v", file, err) + } + return string(content) +} + +func TestApiDirUpload0(t *testing.T) { + // t.Skip("FIXME") + testFileSystem(t, func(fs *FileSystem) { + api := fs.api + bzzhash, err := fs.Upload(path.Join(testDir, "test0"), "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + content := readPath(t, testDir, "test0", "index.html") + resp := testGet(t, api, bzzhash+"/index.html") + exp := expResponse(content, "text/html; charset=utf-8", 0) + checkResponse(t, resp, exp) + + content = readPath(t, testDir, "test0", "index.css") + resp = testGet(t, api, bzzhash+"/index.css") + exp = expResponse(content, "text/css", 0) + checkResponse(t, resp, exp) + + content = readPath(t, testDir, "test0", "img", "logo.png") + resp = testGet(t, api, bzzhash+"/img/logo.png") + exp = expResponse(content, "image/png", 0) + + _, _, _, err = api.Get(bzzhash, true) + if err == nil { + t.Fatalf("expected error: %v", err) + } + + downloadDir := path.Join(testDownloadDir, "test0") + os.RemoveAll(downloadDir) + defer os.RemoveAll(downloadDir) + err = fs.Download(bzzhash, downloadDir) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + newbzzhash, err := fs.Upload(downloadDir, "") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if bzzhash != newbzzhash { + t.Fatalf("download %v reuploaded has incorrect hash, expected %v, got %v", downloadDir, bzzhash, newbzzhash) + } + + }) +} + +func TestApiDirUploadModify(t *testing.T) { + // t.Skip("FIXME") + testFileSystem(t, func(fs *FileSystem) { + api := fs.api + bzzhash, err := fs.Upload(path.Join(testDir, "test0"), "") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + bzzhash, err = api.Modify(bzzhash+"/index.html", "", "", true) + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + bzzhash, err = api.Modify(bzzhash+"/index2.html", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8", true) + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + bzzhash, err = api.Modify(bzzhash+"/img/logo.png", "9ea1f60ebd80786d6005f6b256376bdb494a82496cd86fe8c307cdfb23c99e71", "text/html; charset=utf-8", true) + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + content := readPath(t, testDir, "test0", "index.html") + resp := testGet(t, api, bzzhash+"/index2.html") + exp := expResponse(content, "text/html; charset=utf-8", 0) + checkResponse(t, resp, exp) + + resp = testGet(t, api, bzzhash+"/img/logo.png") + exp = expResponse(content, "text/html; charset=utf-8", 0) + checkResponse(t, resp, exp) + + content = readPath(t, testDir, "test0", "index.css") + resp = testGet(t, api, bzzhash+"/index.css") + exp = expResponse(content, "text/css", 0) + + _, _, _, err = api.Get(bzzhash, true) + if err == nil { + t.Errorf("expected error: %v", err) + } + }) +} + +func TestApiDirUploadWithRootFile(t *testing.T) { + testFileSystem(t, func(fs *FileSystem) { + api := fs.api + bzzhash, err := fs.Upload(path.Join(testDir, "test0"), "index.html") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + content := readPath(t, testDir, "test0", "index.html") + resp := testGet(t, api, bzzhash) + exp := expResponse(content, "text/html; charset=utf-8", 0) + checkResponse(t, resp, exp) + }) +} + +func TestApiFileUpload(t *testing.T) { + testFileSystem(t, func(fs *FileSystem) { + api := fs.api + bzzhash, err := fs.Upload(path.Join(testDir, "test0", "index.html"), "") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + content := readPath(t, testDir, "test0", "index.html") + resp := testGet(t, api, bzzhash+"/index.html") + exp := expResponse(content, "text/html; charset=utf-8", 0) + checkResponse(t, resp, exp) + }) +} + +func TestApiFileUploadWithRootFile(t *testing.T) { + testFileSystem(t, func(fs *FileSystem) { + api := fs.api + bzzhash, err := fs.Upload(path.Join(testDir, "test0", "index.html"), "index.html") + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + content := readPath(t, testDir, "test0", "index.html") + resp := testGet(t, api, bzzhash) + exp := expResponse(content, "text/html; charset=utf-8", 0) + checkResponse(t, resp, exp) + }) +} diff --git a/swarm/api/http/roundtripper.go b/swarm/api/http/roundtripper.go new file mode 100644 index 0000000000..69c501e5be --- /dev/null +++ b/swarm/api/http/roundtripper.go @@ -0,0 +1,53 @@ +package http + +import ( + "fmt" + "net/http" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +/* +http roundtripper to register for bzz url scheme +see https://github.com/ethereum/go-ethereum/issues/2040 +Usage: + +import ( + "github.com/ethereum/go-ethereum/common/httpclient" + "github.com/ethereum/go-ethereum/swarm/api/http" +) +client := httpclient.New() +// for (private) swarm proxy running locally +client.RegisterScheme("bzz", &http.RoundTripper{Port: port}) +client.RegisterScheme("bzzi", &http.RoundTripper{Port: port}) +client.RegisterScheme("bzzr", &http.RoundTripper{Port: port}) + +The port you give the Roundtripper is the port the swarm proxy is listening on. +If Host is left empty, localhost is assumed. + +Using a public gateway, the above few lines gives you the leanest +bzz-scheme aware read-only http client. You really only ever need this +if you need go-native swarm access to bzz addresses, e.g., +github.com/ethereum/go-ethereum/common/natspec + +*/ + +type RoundTripper struct { + Host string + Port string +} + +func (self *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) { + host := self.Host + if len(host) == 0 { + host = "localhost" + } + url := fmt.Sprintf("http://%s:%s/%s:/%s/%s", host, self.Port, req.Proto, req.URL.Host, req.URL.Path) + glog.V(logger.Info).Infof("[BZZ] roundtripper: proxying request '%s' to '%s'", req.RequestURI, url) + reqProxy, err := http.NewRequest(req.Method, url, req.Body) + if err != nil { + return nil, err + } + return http.DefaultClient.Do(reqProxy) +} diff --git a/swarm/api/roundtripper_test.go b/swarm/api/http/roundtripper_test.go similarity index 86% rename from swarm/api/roundtripper_test.go rename to swarm/api/http/roundtripper_test.go index e0b15658dc..1bed107887 100644 --- a/swarm/api/roundtripper_test.go +++ b/swarm/api/http/roundtripper_test.go @@ -1,4 +1,4 @@ -package api +package http import ( "io/ioutil" @@ -22,7 +22,7 @@ func TestRoundTripper(t *testing.T) { }) go http.ListenAndServe(":8600", serveMux) - rt := &RoundTripper{"8600"} + rt := &RoundTripper{Port: "8600"} client := httpclient.New("/") client.RegisterProtocol("bzz", rt) @@ -43,8 +43,8 @@ func TestRoundTripper(t *testing.T) { t.Errorf("expected no error, got %v", err) return } - if string(content) != "/test.com/path" { - t.Errorf("incorrect response from http server: expected '%v', got '%v'", "/test.com/path", string(content)) + if string(content) != "/HTTP/1.1:/test.com/path" { + t.Errorf("incorrect response from http server: expected '%v', got '%v'", "/HTTP/1.1:/test.com/path", string(content)) } } diff --git a/swarm/api/http.go b/swarm/api/http/server.go similarity index 80% rename from swarm/api/http.go rename to swarm/api/http/server.go index b1cd039cce..6ac2b113e6 100644 --- a/swarm/api/http.go +++ b/swarm/api/http/server.go @@ -1,7 +1,7 @@ /* A simple http server interface to Swarm */ -package api +package http import ( "bytes" @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/swarm/api" ) const ( @@ -21,8 +22,8 @@ const ( ) var ( - bzzPrefix = regexp.MustCompile("^/+bzz:/+") - rawUrl = regexp.MustCompile("^/+raw/*") + // accepted protocols: bzz (traditional), bzzi (immutable) and bzzr (raw) + bzzPrefix = regexp.MustCompile("^/+bzz[ir]?:/+") trailingSlashes = regexp.MustCompile("/+$") // forever = func() time.Time { return time.Unix(0, 0) } forever = time.Now @@ -41,7 +42,7 @@ type sequentialReader struct { // https://github.com/atom/electron/blob/master/docs/api/protocol.md // starts up http server -func StartHttpServer(api *Api, port string) { +func StartHttpServer(api *api.Api, port string) { serveMux := http.NewServeMux() serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { handler(w, r, api) @@ -50,7 +51,7 @@ func StartHttpServer(api *Api, port string) { glog.V(logger.Info).Infof("[BZZ] Swarm HTTP proxy started on localhost:%s", port) } -func handler(w http.ResponseWriter, r *http.Request, api *Api) { +func handler(w http.ResponseWriter, r *http.Request, a *api.Api) { requestURL := r.URL // This is wrong // if requestURL.Host == "" { @@ -61,24 +62,41 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) { // return // } // } - glog.V(logger.Debug).Infof("[BZZ] Swarm: HTTP request URL: '%s', Host: '%s', Path: '%s', Referer: '%s', Accept: '%s'", r.RequestURI, requestURL.Host, requestURL.Path, r.Referer(), r.Header.Get("Accept")) + glog.V(logger.Debug).Infof("[BZZ] Swarm: HTTP %s request URL: '%s', Host: '%s', Path: '%s', Referer: '%s', Accept: '%s'", r.Method, r.RequestURI, requestURL.Host, requestURL.Path, r.Referer(), r.Header.Get("Accept")) uri := requestURL.Path - var raw bool + var raw, nameresolver bool + var proto string // HTTP-based URL protocol handler - uri = bzzPrefix.ReplaceAllString(uri, "") glog.V(logger.Debug).Infof("[BZZ] Swarm: BZZ request URI: '%s'", uri) - path := rawUrl.ReplaceAllStringFunc(uri, func(string) string { - raw = true + path := bzzPrefix.ReplaceAllStringFunc(uri, func(p string) string { + proto = p return "" }) - glog.V(logger.Debug).Infof("[BZZ] Swarm: %s request '%s' received.", r.Method, uri) + // protocol identification (ugly) + if proto == "" { + if glog.V(logger.Error) { + glog.Errorf( + "[BZZ] Swarm: Protocol error in request `%s`.", + uri, + ) + http.Error(w, "BZZ protocol error", http.StatusBadRequest) + return + } + } + raw = proto[1:5] == "bzzr" + nameresolver = proto[1:5] != "bzzi" + + glog.V(logger.Debug).Infof( + "[BZZ] Swarm: %s request over protocol %s '%s' received.", + r.Method, proto, path, + ) switch { case r.Method == "POST" || r.Method == "PUT": - key, err := api.dpa.Store(io.NewSectionReader(&sequentialReader{ + key, err := a.Store(io.NewSectionReader(&sequentialReader{ reader: r.Body, ahead: make(map[int64]chan bool), }, 0, r.ContentLength), nil) @@ -102,11 +120,11 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) { http.Error(w, "No PUT to /raw allowed.", http.StatusBadRequest) return } else { - path = regularSlashes(path) + path = api.RegularSlashes(path) mime := r.Header.Get("Content-Type") // TODO proper root hash separation glog.V(logger.Debug).Infof("[BZZ] Modify '%s' to store %v as '%s'.", path, key.Log(), mime) - newKey, err := api.Modify(path[:64], path[65:], common.Bytes2Hex(key), mime) + newKey, err := a.Modify(path, common.Bytes2Hex(key), mime, nameresolver) if err == nil { glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey) w.Header().Set("Content-Type", "text/plain") @@ -122,9 +140,9 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) { http.Error(w, "No DELETE to /raw allowed.", http.StatusBadRequest) return } else { - path = regularSlashes(path) + path = api.RegularSlashes(path) glog.V(logger.Debug).Infof("[BZZ] Delete '%s'.", path) - newKey, err := api.Modify(path[:64], path[65:], "", "") + newKey, err := a.Modify(path, "", "", nameresolver) if err == nil { glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey) w.Header().Set("Content-Type", "text/plain") @@ -138,7 +156,7 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) { path = trailingSlashes.ReplaceAllString(path, "") if raw { // resolving host - key, err := api.Resolve(path) + key, err := a.Resolve(path, nameresolver) if err != nil { glog.V(logger.Error).Infof("[BZZ] Swarm: %v", err) http.Error(w, err.Error(), http.StatusBadRequest) @@ -146,7 +164,7 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) { } // retrieving content - reader := api.dpa.Retrieve(key) + reader := a.Retrieve(key) glog.V(logger.Debug).Infof("[BZZ] Swarm: Reading %d bytes.", reader.Size()) // setting mime type @@ -165,10 +183,9 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) { glog.V(logger.Debug).Infof("[BZZ] Swarm: Structured GET request '%s' received.", uri) - // call to api.getPath on uri - reader, mimeType, status, err := api.getPath(path) + reader, mimeType, status, err := a.Get(path, nameresolver) if err != nil { - if _, ok := err.(errResolve); ok { + if _, ok := err.(api.ErrResolve); ok { glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err) status = http.StatusBadRequest } else { diff --git a/swarm/api/manifest.go b/swarm/api/manifest.go index c1781ab7d3..0337382ff0 100644 --- a/swarm/api/manifest.go +++ b/swarm/api/manifest.go @@ -145,7 +145,10 @@ func (self *manifestTrie) deleteEntry(path string) { b := byte(path[0]) entry := self.entries[b] - if (entry != nil) && (entry.Path == path) { + if entry == nil { + return + } + if entry.Path == path { self.entries[b] = nil return } @@ -290,7 +293,7 @@ func (self *manifestTrie) findPrefixOf(path string) (entry *manifestTrieEntry, p // file system manifest always contains regularized paths // no leading or trailing slashes, only single slashes inside -func regularSlashes(path string) (res string) { +func RegularSlashes(path string) (res string) { for i := 0; i < len(path); i++ { if (path[i] != '/') || ((i > 0) && (path[i-1] != '/')) { res = res + path[i:i+1] @@ -303,7 +306,7 @@ func regularSlashes(path string) (res string) { } func (self *manifestTrie) getEntry(spath string) (entry *manifestTrieEntry, fullpath string) { - path := regularSlashes(spath) + path := RegularSlashes(spath) var pos int entry, pos = self.findPrefixOf(path) return entry, path[:pos] diff --git a/swarm/api/roundtripper.go b/swarm/api/roundtripper.go deleted file mode 100644 index 3dad7d04ef..0000000000 --- a/swarm/api/roundtripper.go +++ /dev/null @@ -1,22 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - - // "github.com/ethereum/go-ethereum/common/httpclient" - // "github.com/ethereum/go-ethereum/jsre" -) - -type RoundTripper struct { - Port string -} - -func (self *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) { - url := fmt.Sprintf("http://localhost:%s/%s/%s", self.Port, req.URL.Host, req.URL.Path) - glog.V(logger.Info).Infof("[BZZ] roundtripper: proxying request '%s' to '%s'", req.RequestURI, url) - return http.Get(url) -} diff --git a/swarm/api/storage.go b/swarm/api/storage.go new file mode 100644 index 0000000000..97b5590fdf --- /dev/null +++ b/swarm/api/storage.go @@ -0,0 +1,48 @@ +package api + +type Response struct { + MimeType string + Status int + Size int64 + // Content []byte + Content string +} + +// implements a service +type Storage struct { + api *Api +} + +func NewStorage(api *Api) *Storage { + return &Storage{api} +} + +// Put uploads the content to the swarm with a simple manifest speficying +// its content type +func (self *Storage) Put(content, contentType string) (string, error) { + return self.api.Put(content, contentType) +} + +// Get retrieves the content from bzzpath and reads the response in full +// It returns the Response object, which serialises containing the +// response body as the value of the Content field +// NOTE: if error is non-nil, sResponse may still have partial content +// the actual size of which is given in len(resp.Content), while the expected +// size is resp.Size +func (self *Storage) Get(bzzpath string) (*Response, error) { + reader, mimeType, status, err := self.api.Get(bzzpath, true) + if err != nil { + return nil, err + } + expsize := reader.Size() + body := make([]byte, expsize) + size, err := reader.Read(body) + if int64(size) == expsize { + err = nil + } + return &Response{mimeType, status, expsize, string(body[:size])}, err +} + +func (self *Storage) Modify(rootHash, path, contentHash, contentType string) (newRootHash string, err error) { + return self.api.Modify(rootHash+"/"+path, contentHash, contentType, true) +} diff --git a/swarm/api/storage_test.go b/swarm/api/storage_test.go new file mode 100644 index 0000000000..44fa7636dc --- /dev/null +++ b/swarm/api/storage_test.go @@ -0,0 +1,33 @@ +package api + +import ( + "testing" +) + +func testStorage(t *testing.T, f func(*Storage)) { + testApi(t, func(api *Api) { + f(NewStorage(api)) + }) +} + +func TestStoragePutGet(t *testing.T) { + testStorage(t, func(api *Storage) { + content := "hello" + exp := expResponse(content, "text/plain", 0) + // exp := expResponse([]byte(content), "text/plain", 0) + bzzhash, err := api.Put(content, exp.MimeType) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + // to check put against the Api#Get + resp0 := testGet(t, api.api, bzzhash) + checkResponse(t, resp0, exp) + + // check storage#Get + resp, err := api.Get(bzzhash) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + checkResponse(t, &testResponse{nil, resp}, exp) + }) +} diff --git a/swarm/api/testapi.go b/swarm/api/testapi.go new file mode 100644 index 0000000000..7e7229bbc8 --- /dev/null +++ b/swarm/api/testapi.go @@ -0,0 +1,30 @@ +package api + +import ( + "github.com/ethereum/go-ethereum/swarm/network" +) + +type Control struct { + api *Api + hive *network.Hive +} + +func NewControl(api *Api, hive *network.Hive) *Control { + return &Control{api, hive} +} + +func (self *Control) BlockNetworkRead(on bool) { + self.hive.BlockNetworkRead(on) +} + +func (self *Control) SyncEnabled(on bool) { + self.hive.SyncEnabled(on) +} + +func (self *Control) SwapEnabled(on bool) { + self.hive.SwapEnabled(on) +} + +func (self *Control) Hive() string { + return self.hive.String() +} diff --git a/swarm/cmd/bzzup.sh b/swarm/cmd/bzzup.sh index dc6490ad70..099a5057af 100755 --- a/swarm/cmd/bzzup.sh +++ b/swarm/cmd/bzzup.sh @@ -1,17 +1,19 @@ #! /bin/bash INDEX='index.html' -port="8500" +proxy="http://localhost:8500" delimiter='{"entries":[{' + if [[ ! -z "$2" ]]; then - port="$2" + proxy="$2" fi if [ -f "$1" ]; then -hash=`wget -q -O- --post-file="$1" http://localhost:$port/raw` +hash=`wget -q -O- --post-file="$1" $proxy/bzzr:/` mime=`mimetype -b "$1"` -wget -q -O- --post-data="$delimiter\"hash\":\"$hash\",\"contentType\":\"$mime\"}]}" http://localhost:8500/raw +# echo wget -q -O- --post-data="$delimiter\"hash\":\"$hash\",\"contentType\":\"$mime\"}]}" $proxy/bzzr:/ +wget -q -O- --post-data="$delimiter\"hash\":\"$hash\",\"contentType\":\"$mime\"}]}" $proxy/bzzr:/ echo else @@ -28,8 +30,11 @@ do name=`echo "$path" | cut -c3-` [ _`basename "$name"` = "_$INDEX" ] && name=`dirname "$name"` echo -n "$delimiter" -hash=`wget -q -O- --post-file="$path" http://localhost:$port/raw` +hash=`wget -q -O- --post-file="$path" $proxy/bzzr:/` mime=`mimetype -b "$path"` +if [ "$mime" = "text/plain" ]; then + echo -n $path|grep -q '.css' && mime="text/css" +fi if [ "_$name" = '_.' ]; then echo -n "\"hash\":\"$hash\",\"contentType\":\"$mime\"" else @@ -38,7 +43,7 @@ fi delimiter='},{' done -echo -n '}]}') | wget -q -O- --post-data=`cat` http://localhost:$port/raw +echo -n '}]}') | wget -q -O- --post-data=`cat` $proxy/bzzr:/ echo popd > /dev/null diff --git a/swarm/cmd/swarm/gethup.sh b/swarm/cmd/swarm/gethup.sh new file mode 100644 index 0000000000..e9b93f69bf --- /dev/null +++ b/swarm/cmd/swarm/gethup.sh @@ -0,0 +1,149 @@ +#!/bin/bash +# Usage: +# bash /path/to/eth-utils/gethup.sh + +root=$1 # base directory to use for datadir and logs +shift +id=$1 # double digit instance id like 00 01 02 +shift +ip_addr=$1 # ip address to substitute +shift + +# logs are output to a date-tagged file for each run , while a link is +# created to the latest, so that monitoring be easier with the same filename +# TODO: use this if GETH not set +# GETH=geth +# echo "ls -l $GETH" +# ls -l $GETH + +# geth CLI params e.g., (dd=04, run=09) +datetag=`date "+%c%y%m%d-%H%M%S"|cut -d ' ' -f 5` +datadir=$root/data/$id # /tmp/eth/04 +log=$root/log/$id.$datetag.log # /tmp/eth/04.09.log +linklog=$root/log/$id.current.log # /tmp/eth/04.09.log +stablelog=$root/log/$id.log # /tmp/eth/04.09.log +password=$id # 04 +port=303$id # 34504 +bzzport=322$id # 32204 +rpcport=302$id # 3204 + +mkdir -p $root/data +mkdir -p $root/enodes +mkdir -p $root/pids +mkdir -p $root/log +ln -sf "$log" "$linklog" +# if we do not have an account, create one +# will not prompt for password, we use the double digit instance id as passwd +# NEVER EVER USE THESE ACCOUNTS FOR INTERACTING WITH A LIVE CHAIN +keystoredir="$datadir/keystore/" +# echo "KeyStore dir: $keystoredir" +if [ ! -d "$keystoredir" ]; then + # echo "create an account with password $id [DO NOT EVER USE THIS ON LIVE]" + # mkdir -p $datadir/keystore + $GETH --datadir $datadir --password <(echo -n $id) account new >/dev/null 2>&1 + # create account with password 00, 01, ... + # note that the account key will be stored also separately outside + # datadir + # this way you can safely clear the data directory and still keep your key + # under `/keystore/dd + # LS=`ls $datadir/keystore` + # echo $LS + while [ ! -d "$keystoredir" ]; do + echo "." + ((i++)) + if ((i>10)); then break; fi + sleep 1 + done + # echo "copying keys $datadir/keystore $root/keystore/$id" + mkdir -p $root/keystore/$id + cp -R "$datadir/keystore/" $root/keystore/$id +fi + +# # mkdir -p $datadir/keystore +# if [ ! -d "$datadir/keystore" ]; then +# echo "copying keys $root/keystore/$id $datadir/keystore" +# cp -R $root/keystore/$id/keystore/ $datadir/keystore/ +# fi + + +# query node's enode url +if [ $ip_addr="" ]; then + pattern='\d+\.\d+\.\d+\.\d+' + ip_addr="[::]" +else + pattern='\[\:\:\]' +fi + +geth="$GETH --datadir $datadir --port $port" + +# echo -n "enode for instance $id... " +if [ ! "$GETH" = "" ] && [ ! -f $root/enodes/$id.enode ]; then + cmd="$geth js <(echo 'console.log(admin.nodeInfo.enode); exit();') " + # echo $cmd '2>/dev/null |grep enode | perl -pe "s/'$pattern'/'$ip_addr'/g" | perl -pe "s/^/\"/; s/\s*$/\"/;" > '$root/enodes/$id.enode + eval $cmd 2>/dev/null |grep enode | perl -pe "s/$pattern/$ip_addr/g" | perl -pe "s/^/\"/; s/\s*\$/\"/;" > $root/enodes/$id.enode +fi +# cat $root/enodes/$id.enode +echo + +# copy cluster enodes list to node's static node list +# echo "copy cluster enodes list to node's static node list" +if [ -f $root/enodes.all ]; then + cp $root/enodes.all $datadir/static-nodes.json +fi + +if [ ! -f $root/pids/$id.pid ]; then + # bring up node `dd` (double digit) + # - using /
+ # - listening on port 303dd, (like 30300, 30301, ...) + # - with the account unlocked + # - launching json-rpc server on port 81dd (like 8100, 8101, 8102, ...) + # echo "BZZKEY=$geth account list|head -n1|perl -ne '/([a-f0-9]{40})/ && print \$1'" + BZZKEY=`$geth account list|head -n1|perl -ne '/([a-f0-9]{40})/ && print \$1'` + echo -n "starting instance $id ($BZZKEY @ $datadir )..." + # echo "$geth \ + # --identity=$id \ + # --bzzaccount=$BZZKEY --bzzport=$bzzport \ + # --unlock=$BZZKEY \ + # --password=<(echo -n $id) \ + # --rpc --rpcport=$rpcport --rpccorsdomain='*' $* \ + # 2>&1 | tee "$stablelog" > "$log" & # comment out if you pipe it to a tty etc. + # " >&2 + + if [ -f $log ] && [ -f $root/stablelog ]; then + cp $stablelog `cat $root/prevlog` + fi + echo $log > $root/prevlog + + $GETH --datadir=$datadir \ + --identity=$id \ + --bzzaccount=$BZZKEY --bzzport=$bzzport \ + --port=$port \ + --unlock=$BZZKEY \ + --password=<(echo -n $id) \ + --rpc --rpcport=$rpcport --rpccorsdomain='*' $* \ + > "$stablelog" 2>&1 & # comment out if you pipe it to a tty etc. + + # wait until ready + # pid=`ps auxwww|grep geth|grep "ty=$id"|grep -v grep|awk '{print $2}'` + # echo "pid: $pid" + # ps auxwww|grep geth|grep "ty=$id"|grep -v grep + # echo $pid > $root/pids/$id.pid + #echo $! > $root/pids/$id.pid + while true; do + $GETH --exec="net" attach ipc:$datadir/geth.ipc > /dev/null 2>&1 && break + sleep 1 + echo -n "." + if ((i++>10)); then + echo "instance $id failed to start" + exit 1 + fi + done + echo -n "started - " + pid=`ps auxwww|grep geth|grep "ty=$id"|grep -v grep|awk '{print $2}'` + echo "pid: $pid" + # ps auxwww|grep geth|grep "ty=$id"|grep -v grep + echo $pid > $root/pids/$id.pid +fi + +# to bring up logs, uncomment +# tail -f $log diff --git a/swarm/cmd/swarm/swarm.sh b/swarm/cmd/swarm/swarm.sh new file mode 100644 index 0000000000..d150e25597 --- /dev/null +++ b/swarm/cmd/swarm/swarm.sh @@ -0,0 +1,316 @@ +# !/bin/bash +# bash cluster [[params]...] +# https://github.com/ethereum/go-ethereum/wiki/Setting-up-monitoring-on-local-cluster + +# sets up a local ethereum network cluster of nodes +# - is the number of nodes in cluster +# - is the root directory for the cluster, the nodes are set up +# with datadir `//00`, `/ /01`, ... +# - new accounts are created for each node +# - they launch on port 30300, 30301, ... +# - they star rpc on port 8100, 8101, ... +# - by collecting the nodes nodeUrl, they get connected to each other +# - if enode has no IP, `` is substituted +# - if `` is not 0, they will not connect to a default client, +# resulting in a private isolated network +# - the nodes log into `//00..log`, `//01..log`, ... +# - The nodes launch in mining mode +# - the cluster can be killed with `killall geth` (FIXME: should record PIDs) +# and restarted from the same state +# - if you want to interact with the nodes, use rpc +# - you can supply additional params on the command line which will be passed +# to each node, for instance `-mine` + +if [ "$GETH" = "" ]; then + echo "env var GETH not set " + exit 1 +fi + +srcdir=`dirname $0` + +root=$1 +shift +network_id=$1 +shift +cmd=$1 +shift +# ip_addr=`curl ipecho.net/plain 2>/dev/null;echo ` + +# echo "external IP: $ip_addr" +swarmoptions='--dev --maxpeers=20 --shh=false --nodiscover' +tmpdir=/tmp + +function attach { + id=$1 + shift + echo "attaching console to instance $id" + cmd="$GETH $* attach ipc:$root/$network_id/data/$id/geth.ipc" + # echo $cmd + eval $cmd +} + +function log { + id=$1 + shift + echo "streaming logs for instance $id" + cmd="tail -f $root/$network_id/log/$id.log" + echo $cmd + eval $cmd +} + +function less { + id=$1 + shift + echo "viewing logs for instance $id" + cmd="/usr/bin/less $root/$network_id/log/$id.log" + echo $cmd + eval $cmd +} + +function start { + id=$1 + shift + # echo -n "starting instance $id - " + cmd="bash $srcdir/gethup.sh $root/$network_id/ $id '$ip_addr' --networkid=$network_id $swarmoptions $*" + # echo "pid="`cat $root/$network_id/pids/$id.pid` + # echo $cmd + eval $cmd +} + +function stop { + id=$1 + shift + if [ $id = "all" ]; then + procs=`cat $root/$network_id/pids/*.pid 2>/dev/null |perl -pe 's/^\s+//;s/\s+\\$//;s/\s+/\n/g'` + # echo "stopping processes $procs" + for p in $procs; do + shutdown $p + done + rm -rf $root/$network_id/pids/* + else + pid=$root/$network_id/pids/$id.pid + if [ -f $pid ]; then + echo "stopping instance $id, pid="`cat $pid` + shutdown `cat $pid` + rm $pid + fi + fi + # ps auxwww|grep geth|grep bzz|grep -v grep +} + +function shutdown { + echo -n "stopping $1..." + kill -2 $1 + while true ;do + ps auxwww|grep geth|grep -v grep|awk '{print $2}'|grep -ql $1 || break + sleep 1 + # ps auxwww|grep geth|grep -v grep |grep $1 #|awk '{print $2}' + done + echo "stopped" +} + +function restart { + id=$1 + shift + stop $id + start $id $* +} + +function init { + stop all + killall geth + reset all + cluster $* + stop all + cluster $* +} + +function reset { + id=$1 + shift + if [ $id = "all" ]; then + rm -rf $root/$network_id + else + rm -rf$root/$network_id/*/$id* + fi + +} + +function cluster { + N=$1 + shift + echo "launching cluster of $N instances" + # cmd="bash $srcdir/gethcluster.sh $root $network_id $N '' $swarmoptions $*" + # echo $cmd + # eval $cmd + dir=$root/$network_id + mkdir -p $dir/data + mkdir -p $dir/enodes + mkdir -p $dir/pids + mkdir -p $dir/log + + enodes=$dir/enodes.all + rm -f $enodes + # build a static nodes(-like) list of all enodes of the local cluster + echo "[" >> $enodes + for ((i=0;i> $enodes + echo "," >> $enodes + fi + done + echo "\"\"]" >> $enodes + + for ((i=0;i tail -f $dir/log/$id.log" + start $id $vmodule $* + done +} + + +function needs { + id=$1 + keyfile=$2 + target=$3 + dir=`dirname $3` + dest=$tmpdir/down + mkdir -p $dest + file=$dest/`basename $target` + rm -f $file + echo -n "waiting for root hash in '$keyfile'..." + while true; do + if [ -f $keyfile ] && [ ! -z $keyfile ]; then + break + fi + sleep 1 + echo -n "." + done + key=`cat $keyfile|tr -d \"` + echo " => $key" + download $id $key $dest && cmp --silent $file $target && echo "PASS" || echo "FAIL" + # && ls -l $keyfile $file $target +} + + +function up { #port, file + echo "Upload file '$2' to node $1... " 1>&2 + file=`basename $2` + attach $1 "--exec 'bzz.upload(\"$2\", \"$file\")'"|tail -n1> /tmp/key + # key=`bash swarm/cmd/bzzup.sh $2 86$1` + cat /tmp/key +} + +function download { + echo "download '$2' from node $1 to '$3'" + # echo attach $1 "--exec 'bzz.download(\"$2\", \"$3\")'" + attach $1 "--exec 'bzz.download(\"$2\", \"$3\")'" > /dev/null +} + + +function down { + echo -n "Download hash '$2' from node $1... " + # echo "wget -O- http://localhost:86$1/$2 > /dev/null 2>&1 && echo 'got it' || echo 'not found'" + # wget -O- http://localhost:86$1/$2 > /dev/null 2>&1 && echo "got it" || echo "not found" + while true; do + attach $1 "--exec 'bzz.get(\"$2\")'" 2> /dev/null |grep -qil "status" && break + sleep 1 + echo -n "." + if ((i++>10)); then + echo "not found" + return + fi + done + echo "found OK" +} + +function clean { #index + echo "Clean up for $1" + rm -rf $root/$network_id/data/$1/{bzz/*/chunks,bzz/*/requests/,bzz/*/bzz-peers.json,chaindata,nodes} +} + +function info { + echo "swarm node information" + echo "ROOTDIR: $root" + echo "DATADIR: $root/$network_id/data/$1" + echo "LOGFILE: $root/$network_id/log/$1.log" + echo "HTTPAPI: http://localhost:322$1" + echo "ETHPORT: 303$1" + echo "RPCPORT: 302$1" + echo "ACCOUNT:" 0x`ls -1 $root/$network_id/data/$1/bzz` + echo "CHEQUEB:" `cat $root/$network_id/data/$1/bzz/*/config.json|grep Contract|awk -F\" '{print $4}'` + echo "ROOTDIR: $root" + echo "DATADIR: $root/$network_id/data/$1" + echo "LOGFILE: $root/$network_id/log/$1.log" +} + + +function status { + attach 00 -exec "'console.log(eth.getBalance(eth.accounts[0])); console.log(eth.getBalance(bzz.info().Swap.Contract)); console.log(chequebook.balance)'" +} + +function netstatconf { + begin=$1 + N=$2 + name_prefix=$3 + ws_server=$4 + ws_secret=$5 + conf="$root/$network_id/$name_prefix.netstat.json" + + echo "writing netstat conf for cluster $name_prefix to $conf" + + echo -e "[" > $conf + + for ((i=$begin;i<$start+$N;++i)); do + id=`printf "%02d" $i` + single_template=" {\n \"name\" : \"$name_prefix-$i\",\n \"cwd\" : \".\",\n \"script\" : \"app.js\",\n \"log_date_format\" : \"YYYY-MM-DD HH:mm Z\",\n \"merge_logs\" : false,\n \"watch\" : false,\n \"exec_interpreter\" : \"node\",\n \"exec_mode\" : \"fork_mode\",\n \"env\":\n {\n \"NODE_ENV\" : \"production\",\n \"RPC_HOST\" : \"localhost\",\n \"RPC_PORT\" : \"302$id\",\n \"INSTANCE_NAME\" : \"$name_prefix-$i\",\n \"WS_SERVER\" : \"$ws_server\",\n \"WS_SECRET\" : \"$ws_secret\",\n }\n }" + + endline="" + if (($i<$N-1)); then + # if [ "$i" -ne "$N" ]; then + endline="," + fi + echo -e "$single_template$endline" >> $conf + done + + echo "]" >> $conf +} + +case $cmd in + "info" ) + info $*;; + "status" ) + status $*;; + "clean" ) + clean $*;; + "needs" ) + needs $*;; + "up" ) + up $*;; + "down" ) + down $*;; + "init" ) + init $*;; + "start" ) + start $*;; + "stop" ) + stop $* ;; + "restart" ) + restart $*;; + "reset" ) + reset $*;; + "cluster" ) + cluster $*;; + "attach" ) + attach $*;; + "log" ) + log $*;; + "less" ) + less $*;; + "netstatconf" ) + netstatconf $*;; + +esac diff --git a/swarm/cmd/swarm/test.sh b/swarm/cmd/swarm/test.sh new file mode 100644 index 0000000000..5ded17493a --- /dev/null +++ b/swarm/cmd/swarm/test.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +TEST_DIR=`dirname $0` +TEST_NAME=`basename $0 .sh` +TEST_TYPE=`basename $TEST_DIR` + + +export SWARM_BIN=$TEST_DIR/../../cmd/swarm +export GETH=$SWARM_BIN/../../../geth +export NETWORKID=322$TEST_NAME +export TMPDIR=~/BZZ/test/$TEST_TYPE +export DATA_ROOT=$TMPDIR/$NETWORKID +# alias swarm='bash $SWARM_BIN/swarm.sh $DATA_ROOT $NETWORKID' +EXTRA_ARGS=$* + +rm -rf $DATA_ROOT + +wait=1 + +function swarm { + # echo bash $SWARM_BIN/swarm.sh $TMPDIR $NETWORKID $* $EXTRA_ARGS + bash $SWARM_BIN/swarm.sh $TMPDIR $NETWORKID $* $EXTRA_ARGS +} + + +function randomfile { + dd if=/dev/urandom of=/dev/stdout bs=1024 count=$1 2>/dev/null +} \ No newline at end of file diff --git a/swarm/examples/album/file-manager.js b/swarm/examples/album/file-manager.js new file mode 100644 index 0000000000..3e3f60deb6 --- /dev/null +++ b/swarm/examples/album/file-manager.js @@ -0,0 +1,135 @@ +function uploadFile(files, nr, uri) { + // when uploading complete - redirect to new address + if (files.length <= nr) { + if (uri != "") { + onUploadingComplete(uri); + } + + return; + } + + var currentFile = files[nr]; + if (isNotImage(currentFile.type)) { + uploadFile(files, nr + 1, uri); + return; + } + + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + var newHash = xhr.responseText; + console.log("New hash - " + newHash); + if (newHash.length != 64) { + // something wrong + console.log("Something wrong on uploading"); + alert('Oh, error on PUT file to BZZ. See log for more information.'); + + return; + } + + insertImage(files, nr, newHash, currentFile.name, function () { + uploadFile(files, nr + 1, "/bzz:/" + newHash + "/"); + }); + } + }; + xhr.open("PUT", uri + "imgs/" + currentFile.name, true); + xhr.setRequestHeader('Content-Type', currentFile.type); + + readFile(currentFile, function (result) { + xhr.send(result); + }); +} + +function readFile(file, onComplete) { + var reader = new FileReader(); + reader.onload = function (evt) { + if (onComplete) { + onComplete(evt.target.result) + } + }; + reader.readAsArrayBuffer(file); +} + +function insertImage(files, nr, newHash, fileName, onComplete) { + // insert image into index + var img = new Image(); + img.onload = function () { + var blur = imageToUrl(img, 5, 5); + var thumbData = []; + var thumbSize = 200; + if (img.naturalWidth > img.naturalHeight) { + // landscape thumbnail + var h = img.naturalHeight * thumbSize / img.naturalWidth; + thumbData[0] = imageToUrl(img, thumbSize, h); + thumbData[1] = [thumbSize, h]; + } else if (img.naturalWidth < img.naturalHeight) { + // portrait thumbnail + var w = img.naturalWidth * thumbSize / img.naturalHeight; + thumbData[0] = imageToUrl(img, w, thumbSize); + thumbData[1] = [w, thumbSize]; + } else { + // square + thumbData[0] = imageToUrl(img, thumbSize, thumbSize); + thumbData[1] = [thumbSize, thumbSize]; + } + + jQuery('#currentPreview').attr('src', thumbData[0]); + // update index + var imgData = []; + imgData[0] = "imgs/" + fileName; + imgData[1] = [img.naturalWidth, img.naturalHeight]; + imgs.data.splice(eidx, 0, {img: imgData, thumb: thumbData, blur: blur}); + if (onComplete) { + onComplete(); + } + }; + + img.onerror = function () { + if (onComplete) { + onComplete(); + } + }; + + img.src = "/bzz:/" + newHash + "/imgs/" + fileName; +} + +function isNotImage(type) { + var imageType = /^image\//; + return !imageType.test(type); +} + +function onUploadingComplete(uri) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + var i = xhr.responseText; + window.location.replace("/bzz:/" + i + "/"); + } + }; + sendImages(xhr, uri); +} + +function handleFiles(files) { + uploadFile(files, 0, ""); +} + +function sendImages(xhr, uri) { + // set up request + xhr.open("PUT", uri + "data.json", true); + xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); + // send the collected data as JSON + xhr.send(JSON.stringify(imgs)); +} + +// do it because I love jQuery +function jqueryInit() { + // setup upload file selector + var fileElem = jQuery("#fileElem"); + jQuery("#fileSelect").on("click", function (e) { + if (fileElem) { + fileElem.click(); + } + + e.preventDefault(); + }); +} \ No newline at end of file diff --git a/swarm/examples/album/images/favicon.ico b/swarm/examples/album/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..95740c89b0738b570986c703cf401facfab64072 GIT binary patch literal 305 zcmV-10nYx3P)kdg0002^NklOj*u)m_z7d#P=wTq0f7VQQgR#iU(b=FfLMFbetv zc5$aNUE%FvWr^MRnL1G=?~J^24_|+-KVKe@yXyY{<*<3`(XlR#00000NkvXXu0mjf DmUx07 literal 0 HcmV?d00001 diff --git a/swarm/examples/album/index.css b/swarm/examples/album/index.css index b77c3198ce..a550228d50 100644 --- a/swarm/examples/album/index.css +++ b/swarm/examples/album/index.css @@ -289,3 +289,7 @@ img { background: url(cut-mov.png) top left repeat-y, url(cut-mov.png) top right repeat-y; } + +#currentPreview { + max-height: 120px; +} \ No newline at end of file diff --git a/swarm/examples/album/index.html b/swarm/examples/album/index.html index e42413c030..0f73c72798 100644 --- a/swarm/examples/album/index.html +++ b/swarm/examples/album/index.html @@ -1,20 +1,26 @@ - - - + + + + + + + - - - - - - - + + + + + + + diff --git a/swarm/examples/album/index.js b/swarm/examples/album/index.js index 5bce32b3e3..c1dd51771b 100644 --- a/swarm/examples/album/index.js +++ b/swarm/examples/album/index.js @@ -2,7 +2,8 @@ // Copyright(c) 2003-2014 by wave++ "Yuri D'Elia" // Distributed under GPL2 (see COPYING) WITHOUT ANY WARRANTY. var datafile = 'data.json'; -var padding = 22; +var padding = 100; +var marginTop = 50; var duration = 500; var thrdelay = 1500; var hidedelay = 3000; @@ -41,6 +42,7 @@ var eback; // background var enoise; // additive noise var eflash; // flashing object var ehdr; // header +var progress; // progress var elist; // thumbnail list var fscr; // thumbnail list scroll fx var econt; // picture container @@ -231,7 +233,7 @@ function resizeMainImg(img) img.setStyles( { 'position': 'absolute', - 'top': contSize.y / 2 - img.height / 2, + 'top': (contSize.y / 2 - img.height / 2) + marginTop, 'left': contSize.x / 2 - img.width / 2 }); } @@ -264,15 +266,6 @@ function centerThumb(duration) fscr = new Fx.Scroll(elist, { duration: duration }).start(x, y); } -function sendImgs(xhr, uri) { - // set up request - xhr.open("PUT", uri + "data.json", true); - xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); - - // send the collected data as JSON - xhr.send(JSON.stringify(imgs)); -} - function imageToUrl(img, w, h) { var can = document.createElement('canvas'); can.width = w; @@ -282,70 +275,6 @@ function imageToUrl(img, w, h) { return can.toDataURL(); } -function uploadFile(files, nr, uri) { - if(files.length <= nr) { - if(uri != "") { - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function() { if (xhr.readyState === 4) { - var i = xhr.responseText; - window.location.replace("/" + i + "/"); - }}; - sendImgs(xhr, uri); - } - return; - } - var imageType = /^image\//; - var file = files[nr]; - if(!imageType.test(file.type)) { - uploadFile(files, nr + 1, uri); - return; - } - - var xhr = new XMLHttpRequest(); - xhr.onreadystatechange = function() { if (xhr.readyState === 4) { - var i = xhr.responseText; - - // insert image into index - var img = new Image(); - img.onload = function() { - var blur = imageToUrl(img, 5, 5); - var thumbData = []; - var thumbSize = 200; - if(img.naturalWidth > img.naturalHeight) { - // landscape thumbnail - var h = img.naturalHeight * thumbSize / img.naturalWidth; - thumbData[0] = imageToUrl(img, thumbSize, h); - thumbData[1] = [thumbSize, h]; - } else { - // portrait thumbnail - var w = img.naturalWidth * thumbSize / img.naturalHeight; - thumbData[0] = imageToUrl(img, w, thumbsize); - thumbData[1] = [w, thumbSize]; - } - // update index - var imgData = []; - imgData[0] = "imgs/" + file.name; - imgData[1] = [img.naturalWidth, img.naturalHeight]; - imgs.data.splice(eidx, 0, {img: imgData, thumb: thumbData, blur: blur}); - uploadFile(files, nr + 1, "/" + i + "/"); - } - img.src = "/" + i + "/imgs/" + file.name; - return; - }}; - xhr.open("PUT", uri + "imgs/" + file.name, true); - xhr.setRequestHeader('Content-Type', file.type); - - var reader = new FileReader(); - reader.onload = function(evt) { - xhr.send(evt.target.result); - }; - reader.readAsArrayBuffer(file); -} - -function handleFiles(files) { - uploadFile(files, 0, ""); -} - function deleteImg() { if(imgs.data.length < 2) return; // empty albums not allowed @@ -361,13 +290,13 @@ function deleteImg() var xhrd = new XMLHttpRequest(); xhrd.onreadystatechange = function () { if (xhrd.readyState === 4) { var j = xhrd.responseText; - window.location.replace("/" + j + "/"); + window.location.replace("/bzz:/" + j + "/"); }}; - xhrd.open("DELETE", "/" + i + "/" + fname, true); + xhrd.open("DELETE", "/bzz%3A/" + i + "/" + fname, true); xhrd.send(); }}; - sendImgs(xhr, ""); + sendImages(xhr, ""); } function moveUpDown(off) @@ -378,9 +307,9 @@ function moveUpDown(off) var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if (xhr.readyState === 4) { var i = xhr.responseText; - window.location.replace("/" + i + "/#" + (eidx + off)); + window.location.replace("/bzz:/" + i + "/#" + (eidx + off)); }}; - sendImgs(xhr, ""); + sendImages(xhr, ""); } function moveUp() @@ -435,15 +364,8 @@ function onMainReady() ehdr.set('html', dsc.join(' ')); ehdr.setStyle('display', (dsc.length? 'block': 'none')); - // setup upload file selector - var fileSelect = document.getElementById("fileSelect"), - fileElem = document.getElementById("fileElem"); - fileSelect.addEventListener("click", function (e) { - if (fileElem) { - fileElem.click(); - } - e.preventDefault(); // prevent navigation to "#" - }, false); + progress.set('html', ''); + progress.set('style', 'text-align: center; padding-top: 20px'); // complete thumbnails var d = duration; @@ -494,6 +416,8 @@ function onMainReady() var data = imgs.data[eidx + 1]; Asset.images([data.img[0], data.blur]); } + + jqueryInit(); } function showThrobber() @@ -677,6 +601,9 @@ function initGallery(data) ehdr = new Element('div', { id: 'header' }); ehdr.inject(econt); + progress = new Element('div', { id: 'progress' }); + progress.inject(econt); + elist = new Element('div', { id: 'list' }); elist.inject(emain); diff --git a/swarm/examples/album/jquery.js b/swarm/examples/album/jquery.js new file mode 100644 index 0000000000..b8c4187de1 --- /dev/null +++ b/swarm/examples/album/jquery.js @@ -0,0 +1,4 @@ +/*! jQuery v2.2.3 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.3",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c; +}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/\s*$/g;function pa(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function qa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function ra(a){var b=na.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function sa(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(N.hasData(a)&&(f=N.access(a),g=N.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}O.hasData(a)&&(h=O.access(a),i=n.extend({},h),O.set(b,i))}}function ta(a,b){var c=b.nodeName.toLowerCase();"input"===c&&X.test(a.type)?b.checked=a.checked:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}function ua(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&ma.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),ua(f,b,c,d)});if(o&&(e=ca(b,a[0].ownerDocument,!1,a,d),g=e.firstChild,1===e.childNodes.length&&(e=g),g||d)){for(h=n.map(_(e,"script"),qa),i=h.length;o>m;m++)j=e,m!==p&&(j=n.clone(j,!0,!0),i&&n.merge(h,_(j,"script"))),c.call(a[m],j,m);if(i)for(k=h[h.length-1].ownerDocument,n.map(h,ra),m=0;i>m;m++)j=h[m],Z.test(j.type||"")&&!N.access(j,"globalEval")&&n.contains(k,j)&&(j.src?n._evalUrl&&n._evalUrl(j.src):n.globalEval(j.textContent.replace(oa,"")))}return a}function va(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(_(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&aa(_(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(ka,"<$1>")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=_(h),f=_(a),d=0,e=f.length;e>d;d++)ta(f[d],g[d]);if(b)if(c)for(f=f||_(a),g=g||_(h),d=0,e=f.length;e>d;d++)sa(f[d],g[d]);else sa(a,h);return g=_(h,"script"),g.length>0&&aa(g,!i&&_(a,"script")),h},cleanData:function(a){for(var b,c,d,e=n.event.special,f=0;void 0!==(c=a[f]);f++)if(L(c)){if(b=c[N.expando]){if(b.events)for(d in b.events)e[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);c[N.expando]=void 0}c[O.expando]&&(c[O.expando]=void 0)}}}),n.fn.extend({domManip:ua,detach:function(a){return va(this,a,!0)},remove:function(a){return va(this,a)},text:function(a){return K(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.appendChild(a)}})},prepend:function(){return ua(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=pa(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return ua(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(_(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return K(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!la.test(a)&&!$[(Y.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(_(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return ua(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(_(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),f=e.length-1,h=0;f>=h;h++)c=h===f?this:this.clone(!0),n(e[h])[b](c),g.apply(d,c.get());return this.pushStack(d)}});var wa,xa={HTML:"block",BODY:"block"};function ya(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function za(a){var b=d,c=xa[a];return c||(c=ya(a,b),"none"!==c&&c||(wa=(wa||n("