From 03b9ae7cf654571c154eb767cab43cc0c5f0de6b 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 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 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 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 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 | 194 +++++- 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 | 258 ++++++++ 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 | 56 ++ swarm/api/storage_test.go | 33 + swarm/api/testapi.go | 28 + 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/index.js | 12 +- swarm/examples/bzzhandler.html | 13 +- swarm/network/depo.go | 9 +- swarm/network/forwarding.go | 37 +- swarm/network/hive.go | 52 +- {common => swarm/network}/kademlia/address.go | 0 .../network}/kademlia/address_test.go | 0 {common => swarm/network}/kademlia/kaddb.go | 0 .../network}/kademlia/kademlia.go | 101 ++-- .../network}/kademlia/kademlia_test.go | 0 swarm/network/messages.go | 13 +- swarm/network/protocol.go | 83 ++- swarm/network/syncer.go | 46 +- 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 | 120 ++-- {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 | 156 +++-- 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 + 80 files changed, 4030 insertions(+), 4138 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 rename {common => swarm/network}/kademlia/address.go (100%) rename {common => swarm/network}/kademlia/address_test.go (100%) rename {common => swarm/network}/kademlia/kaddb.go (100%) rename {common => swarm/network}/kademlia/kademlia.go (84%) 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..6865c7d72f 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -18,17 +18,181 @@ 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: '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: '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: + [ + ] +}); +` + +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 +340,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..e1ab01318e 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.Debug).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.Debug).Infof("[BZZ] DNS error : %v", err) + } + glog.V(logger.Debug).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.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 +} + +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..3bd4a318d3 --- /dev/null +++ b/swarm/api/filesystem.go @@ -0,0 +1,258 @@ +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 + //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.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.Debug).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..82b8f12fba --- /dev/null +++ b/swarm/api/storage.go @@ -0,0 +1,56 @@ +package api + +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/storage" +) + +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 + } + glog.V(logger.Detail).Infof("body: %s", body[:size]) + 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..339b623089 --- /dev/null +++ b/swarm/api/testapi.go @@ -0,0 +1,28 @@ +package api + +import ( + // "fmt" + + "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) +} 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/index.js b/swarm/examples/album/index.js index 5bce32b3e3..aa7b46054d 100644 --- a/swarm/examples/album/index.js +++ b/swarm/examples/album/index.js @@ -288,7 +288,7 @@ function uploadFile(files, nr, uri) { var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { var i = xhr.responseText; - window.location.replace("/" + i + "/"); + window.location.replace("/bzz:/" + i + "/"); }}; sendImgs(xhr, uri); } @@ -327,9 +327,9 @@ function uploadFile(files, nr, uri) { 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 + "/"); + uploadFile(files, nr + 1, "/bzz:/" + i + "/"); } - img.src = "/" + i + "/imgs/" + file.name; + img.src = "/bzz:/" + i + "/imgs/" + file.name; return; }}; xhr.open("PUT", uri + "imgs/" + file.name, true); @@ -361,9 +361,9 @@ 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(); }}; @@ -378,7 +378,7 @@ 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, ""); } diff --git a/swarm/examples/bzzhandler.html b/swarm/examples/bzzhandler.html index 0d7a8b01a4..36b756561f 100644 --- a/swarm/examples/bzzhandler.html +++ b/swarm/examples/bzzhandler.html @@ -6,10 +6,21 @@ navigator.registerProtocolHandler("bzz", "http://localhost:8500/%s", "Swarm handler"); + navigator.registerProtocolHandler("bzzi", + "http://localhost:8500/%s", + "Swarm immutable handler"); + navigator.registerProtocolHandler("bzzr", + "http://localhost:8500/%s", + "Swarm raw handler");

Register Swarm protocol handler

-

This web page will install a web protocol handler for the bzz: protocol.

+

This web page will install a web protocol handler for + bzz:, + bzzi: and + bzzr: + protocols. +

diff --git a/swarm/network/depo.go b/swarm/network/depo.go index eeb50f4595..3b91920f03 100644 --- a/swarm/network/depo.go +++ b/swarm/network/depo.go @@ -5,9 +5,9 @@ import ( "encoding/binary" "time" - "github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/swarm/storage" ) // Handler for storage/retrieval related protocol requests @@ -51,6 +51,7 @@ func (self *Depo) HandleUnsyncedKeysMsg(req *unsyncedKeysMsgData, p *peer) error if err != nil { return err } + // set peers state to persist p.syncState = req.State return nil } @@ -124,7 +125,11 @@ func (self *Depo) HandleRetrieveRequestMsg(req *retrieveRequestMsgData, p *peer) req.from = p // swap - record credit for 1 request // note that only charge actual reqsearches - if err := p.swap.Add(1); err != nil { + var err error + if p.swap != nil { + err = p.swap.Add(1) + } + if err != nil { glog.V(logger.Warn).Infof("[BZZ] Depo.HandleRetrieveRequest: %v - cannot process request: %v", req.Key.Log(), err) return } diff --git a/swarm/network/forwarding.go b/swarm/network/forwarding.go index 46c7808bd1..b859880d81 100644 --- a/swarm/network/forwarding.go +++ b/swarm/network/forwarding.go @@ -4,9 +4,9 @@ import ( "math/rand" "time" - "github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/swarm/storage" ) const requesterCount = 3 @@ -18,7 +18,6 @@ via the native bzz protocol which uses an MSB logarithmic distance-based semi-permanent Kademlia table for * recursive forwarding style routing for retrieval * smart syncronisation -* TODO: beeline delivery, IPFS, IPΞS */ type forwarder struct { @@ -42,26 +41,30 @@ var searchTimeout = 3 * time.Second func (self *forwarder) Retrieve(chunk *storage.Chunk) { peers := self.hive.getPeers(chunk.Key, 0) glog.V(logger.Detail).Infof("[BZZ] forwarder.Retrieve: %v - received %d peers from KΛÐΞMLIΛ...", chunk.Key.Log(), len(peers)) +OUT: for _, p := range peers { glog.V(logger.Detail).Infof("[BZZ] forwarder.Retrieve: sending retrieveRequest %v to peer [%v]", chunk.Key.Log(), p) - var req *retrieveRequestMsgData - OUT: for _, recipients := range chunk.Req.Requesters { for _, recipient := range recipients { req := recipient.(*retrieveRequestMsgData) if req.from.Addr() == p.Addr() { - break OUT + continue OUT } } } - if req != nil { - if err := p.swap.Add(-1); err == nil { - p.retrieve(req) - break - } else { - glog.V(logger.Warn).Infof("[BZZ] forwarder.Retrieve: unable to send retrieveRequest to peer [%v]: %v", chunk.Key.Log(), err) - } + req := &retrieveRequestMsgData{ + Key: chunk.Key, + Id: generateId(), } + var err error + if p.swap != nil { + err = p.swap.Add(-1) + } + if err == nil { + p.retrieve(req) + break OUT + } + glog.V(logger.Warn).Infof("[BZZ] forwarder.Retrieve: unable to send retrieveRequest to peer [%v]: %v", chunk.Key.Log(), err) } } @@ -79,14 +82,14 @@ func (self *forwarder) Store(chunk *storage.Chunk) { source = chunk.Source.(*peer) } for _, p := range self.hive.getPeers(chunk.Key, 0) { - glog.V(logger.Detail).Infof("[BZZ] %v %v", p, chunk) + glog.V(logger.Detail).Infof("[BZZ] forwarder.Store: %v %v", p, chunk) - if source == nil || p.Addr() != source.Addr() { + if p.syncer != nil && (source == nil || p.Addr() != source.Addr()) { n++ Deliver(p, msg, PropagateReq) } } - glog.V(logger.Detail).Infof("[BZZ] forwarder.Store: sent to %v ps (chunk = %v)", n, chunk) + glog.V(logger.Detail).Infof("[BZZ] forwarder.Store: sent to %v peers (chunk = %v)", n, chunk) } // once a chunk is found deliver it to its requesters unless timed out @@ -104,7 +107,7 @@ func (self *forwarder) Deliver(chunk *storage.Chunk) { for id, r := range requesters { req = r.(*retrieveRequestMsgData) if req.timeout == nil || req.timeout.After(time.Now()) { - glog.V(logger.Ridiculousness).Infof("[BZZ] forwarder.Deliver: %v -> %v", req.Id, req.from) + glog.V(logger.Detail).Infof("[BZZ] forwarder.Deliver: %v -> %v", req.Id, req.from) msg.Id = uint64(id) Deliver(req.from, msg, DeliverReq) n++ @@ -114,7 +117,7 @@ func (self *forwarder) Deliver(chunk *storage.Chunk) { } } } - glog.V(logger.Detail).Infof("[BZZ] NetStore.Deliver: submit chunk %v (request id %v) for delivery to %v peers", chunk.Key.Log(), id, n) + glog.V(logger.Detail).Infof("[BZZ] forwarder.Deliver: submit chunk %v (request id %v) for delivery to %v peers", chunk.Key.Log(), id, n) } } diff --git a/swarm/network/hive.go b/swarm/network/hive.go index 1850f8fa57..fd8012350a 100644 --- a/swarm/network/hive.go +++ b/swarm/network/hive.go @@ -7,10 +7,10 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/kademlia" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/swarm/network/kademlia" "github.com/ethereum/go-ethereum/swarm/storage" ) @@ -32,13 +32,19 @@ type Hive struct { path string toggle chan bool more chan bool + + // for testing only + swapEnabled bool + syncEnabled bool + blockRead bool + blockWrite bool } const ( - callInterval = 10000000000 - bucketSize = 3 - maxProx = 10 - proxBinSize = 8 + callInterval = 3000000000 + // bucketSize = 3 + // maxProx = 8 + // proxBinSize = 4 ) type HiveParams struct { @@ -49,9 +55,9 @@ type HiveParams struct { func NewHiveParams(path string) *HiveParams { kad := kademlia.NewKadParams() - kad.BucketSize = bucketSize - kad.MaxProx = maxProx - kad.ProxBinSize = proxBinSize + // kad.BucketSize = bucketSize + // kad.MaxProx = maxProx + // kad.ProxBinSize = proxBinSize return &HiveParams{ CallInterval: callInterval, @@ -60,16 +66,34 @@ func NewHiveParams(path string) *HiveParams { } } -func NewHive(addr common.Hash, params *HiveParams) *Hive { +func NewHive(addr common.Hash, params *HiveParams, swapEnabled, syncEnabled bool) *Hive { kad := kademlia.New(kademlia.Address(addr), params.KadParams) return &Hive{ callInterval: params.CallInterval, kad: kad, addr: kad.Addr(), path: params.KadDbPath, + swapEnabled: swapEnabled, + syncEnabled: syncEnabled, } } +func (self *Hive) SyncEnabled(on bool) { + self.syncEnabled = on +} + +func (self *Hive) SwapEnabled(on bool) { + self.swapEnabled = on +} + +func (self *Hive) BlockNetworkRead(on bool) { + self.blockRead = on +} + +func (self *Hive) BlockNetworkWrite(on bool) { + self.blockWrite = on +} + // public accessor to the hive base address func (self *Hive) Addr() kademlia.Address { return self.addr @@ -122,7 +146,7 @@ func (self *Hive) Start(id discover.NodeID, listenAddr func() string, connectPee } else { self.toggle <- false } - glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: queen's address: %v, population: %d (%d)\n%v", self.addr, self.kad.Count(), self.kad.DBCount(), self.kad) + glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: queen's address: %v, population: %d (%d)", self.addr, self.kad.Count(), self.kad.DBCount()) } }() return @@ -134,7 +158,7 @@ func (self *Hive) Start(id discover.NodeID, listenAddr func() string, connectPee // wake state is toggled by writing to self.toggle // it restarts if the table becomes non-full again due to disconnections func (self *Hive) keepAlive() { - var alarm <-chan time.Time + alarm := time.NewTicker(time.Duration(self.callInterval)).C for { select { case <-alarm: @@ -262,10 +286,14 @@ func (self *peer) LastActive() time.Time { func loadSync(record *kademlia.NodeRecord, node kademlia.Node) error { if p, ok := node.(*peer); ok { if record.Meta == nil { - glog.V(logger.Debug).Infof("no sync state for node record %v", record) + glog.V(logger.Debug).Infof("no sync state for node record %v setting default", record) + p.syncState = &syncState{DbSyncState: &storage.DbSyncState{}} return nil } state, err := decodeSync(record.Meta) + if err != nil { + return fmt.Errorf("error decoding kddb record meta info into a sync state: %v", err) + } glog.V(logger.Debug).Infof("sync state for node record %v: %s -> %v", record, string(*(record.Meta)), state) p.syncState = state return err diff --git a/common/kademlia/address.go b/swarm/network/kademlia/address.go similarity index 100% rename from common/kademlia/address.go rename to swarm/network/kademlia/address.go diff --git a/common/kademlia/address_test.go b/swarm/network/kademlia/address_test.go similarity index 100% rename from common/kademlia/address_test.go rename to swarm/network/kademlia/address_test.go diff --git a/common/kademlia/kaddb.go b/swarm/network/kademlia/kaddb.go similarity index 100% rename from common/kademlia/kaddb.go rename to swarm/network/kademlia/kaddb.go diff --git a/common/kademlia/kademlia.go b/swarm/network/kademlia/kademlia.go similarity index 84% rename from common/kademlia/kademlia.go rename to swarm/network/kademlia/kademlia.go index ac80e8b406..d7308c7bc3 100644 --- a/common/kademlia/kademlia.go +++ b/swarm/network/kademlia/kademlia.go @@ -12,8 +12,9 @@ import ( ) const ( - bucketSize = 20 - maxProx = 255 + bucketSize = 3 + proxBinSize = 4 + maxProx = 8 connRetryExp = 2 ) @@ -35,7 +36,7 @@ type KadParams struct { func NewKadParams() *KadParams { return &KadParams{ MaxProx: maxProx, - ProxBinSize: bucketSize, + ProxBinSize: proxBinSize, BucketSize: bucketSize, PurgeInterval: purgeInterval, InitialRetryInterval: initialRetryInterval, @@ -72,6 +73,8 @@ func New(addr Address, params *KadParams) *Kademlia { } glog.V(logger.Info).Infof("[KΛÐ] base address %v", addr) + // ! temporary hack fixme: + params.ProxBinSize = 4 return &Kademlia{ addr: addr, KadParams: params, @@ -100,6 +103,9 @@ func (self *Kademlia) DBCount() int { // On is the entry point called when a new nodes is added // unsafe in that node is not checked to be already active node (to be called once) func (self *Kademlia) On(node Node, cb func(*NodeRecord, Node) error) (err error) { + defer self.lock.Unlock() + self.lock.Lock() + index := self.proximityBin(node.Addr()) record := self.db.findOrCreate(index, node.Addr(), node.Url()) // callback on add node @@ -116,25 +122,20 @@ func (self *Kademlia) On(node Node, cb func(*NodeRecord, Node) error) (err error } record.connected = true - defer self.lock.Unlock() - self.lock.Lock() - // insert in kademlia table of active nodes bucket := self.buckets[index] // if bucket is full insertion replaces the worst node // TODO: give priority to peers with active traffic if worst, pos := bucket.insert(node); worst != nil { - glog.V(logger.Info).Infof("[KΛÐ]: replace node %v (%d) with node %v", worst, pos, node) + glog.V(logger.Info).Infof("[KΛÐ]: replace node %v (%d) with node %v\n%v", worst, pos, node, self) + return // no prox adjustment needed // do not change count - } else { - glog.V(logger.Info).Infof("[KΛÐ]: add node %v to table", node) - self.count++ - self.adjustProxMore(index) } - + glog.V(logger.Info).Infof("[KΛÐ]: add node %v to table", node) + self.count++ + self.setProxLimit(index, false) return - } // is the entrypoint called when a node is taken offline @@ -161,7 +162,8 @@ func (self *Kademlia) Off(node Node, cb func(*NodeRecord, Node)) (err error) { if len(bucket.nodes) < bucket.size { err = fmt.Errorf("insufficient nodes (%v) in bucket %v", len(bucket.nodes), index) } - self.adjustProxLess(index) + + self.setProxLimit(index, true) r := self.db.index[node.Addr()] // callback on remove @@ -174,49 +176,40 @@ func (self *Kademlia) Off(node Node, cb func(*NodeRecord, Node)) (err error) { return } -// proxLimit is dynamically adjusted so that 1) there is no -// empty buckets in bin < proxLimit and 2) the sum of all items sare the maximum -// possible but lower than ProxBinSize -// adjust Prox (proxLimit and proxSize after an insertion of add nodes into bucket r) -func (self *Kademlia) adjustProxMore(r int) { - if r >= self.proxLimit { - exLimit := self.proxLimit - exSize := self.proxSize - self.proxSize++ - - var i int - for i = self.proxLimit; i < self.MaxProx && len(self.buckets[i].nodes) > 0 && self.proxSize-len(self.buckets[i].nodes) > self.ProxBinSize; i++ { - self.proxSize -= len(self.buckets[i].nodes) - } - self.proxLimit = i - - glog.V(logger.Detail).Infof("[KΛÐ]: Max Prox Bin: Lower Limit: %v (was %v): Bin Size: %v (was %v)", self.proxLimit, exLimit, self.proxSize, exSize) +// proxLimit is dynamically adjusted so that +// 1) there is no empty buckets in bin < proxLimit and +// 2) the sum of all items sare the maximpossible but lower than ProxBinSize +// adjust Prox (proxLimit and proxSize after an insertion/removal of nodes) +// caller holds the lock +func (self *Kademlia) setProxLimit(r int, off bool) { + // glog.V(logger.Info).Infof("[KΛÐ]: adjust proxbin for (bin: %v, off: %v)", r, off) + if r < self.proxLimit && len(self.buckets[r].nodes) > 0 { + return } -} - -func (self *Kademlia) adjustProxLess(r int) { - exLimit := self.proxLimit - exSize := self.proxSize - if r >= self.proxLimit { + glog.V(logger.Detail).Infof("[KΛÐ]: set proxbin (size: %v, limit: %v, bin: %v, off: %v)", self.proxSize, self.proxLimit, r, off) + if off { self.proxSize-- - } - - if r < self.proxLimit && len(self.buckets[r].nodes) == 0 { - for i := self.proxLimit - 1; i > r; i-- { - self.proxSize += len(self.buckets[i].nodes) + for (self.proxSize < self.ProxBinSize || r < self.proxLimit) && + self.proxLimit > 0 { + // + self.proxLimit-- + self.proxSize += len(self.buckets[self.proxLimit].nodes) + glog.V(logger.Detail).Infof("[KΛÐ]: proxbin expansion (size: %v, limit: %v, bin: %v, off: %v)", self.proxSize, self.proxLimit, r, off) } - self.proxLimit = r - } else if self.proxLimit > 0 && r >= self.proxLimit-1 { - var i int - for i = self.proxLimit - 1; i > 0 && len(self.buckets[i].nodes)+self.proxSize <= self.ProxBinSize; i-- { - self.proxSize += len(self.buckets[i].nodes) - } - self.proxLimit = i + glog.V(logger.Detail).Infof("%v", self) + return } + self.proxSize++ + for self.proxLimit < self.MaxProx && + len(self.buckets[self.proxLimit].nodes) > 0 && + self.proxSize-len(self.buckets[self.proxLimit].nodes) >= self.ProxBinSize { + // + self.proxSize -= len(self.buckets[self.proxLimit].nodes) + self.proxLimit++ + glog.V(logger.Detail).Infof("[KΛÐ]: proxbin contraction (size: %v, limit: %v, bin: %v, off: %v)", self.proxSize, self.proxLimit, r, off) + } + glog.V(logger.Detail).Infof("%v", self) - if exLimit != self.proxLimit || exSize != self.proxSize { - glog.V(logger.Detail).Infof("[KΛÐ]: Max Prox Bin: Lower Limit: %v (was %v): Bin Size: %v (was %v)", self.proxLimit, exLimit, self.proxSize, exSize) - } } /* @@ -251,8 +244,7 @@ func (self *Kademlia) FindClosest(target Address, max int) []Node { r.push(bucket[i], limit) n++ } - if max == 0 && start <= index && (n > 0 || start == 0) || - max > 0 && down && start <= index && (n >= limit || n == self.count || start == 0) { + if max == 0 && start <= index && (n > 0 || start == 0) || max > 0 && down && start <= index && (n >= limit || n == self.count || start == 0) { break } if down { @@ -415,6 +407,7 @@ func (self *Kademlia) Load(path string, cb func(*NodeRecord, Node) error) (err e } // kademlia table + kaddb table displayed with ascii +// callerholds the lock func (self *Kademlia) String() string { var rows []string diff --git a/common/kademlia/kademlia_test.go b/swarm/network/kademlia/kademlia_test.go similarity index 100% rename from common/kademlia/kademlia_test.go rename to swarm/network/kademlia/kademlia_test.go diff --git a/swarm/network/messages.go b/swarm/network/messages.go index 4e49be4dbf..31bdf19b54 100644 --- a/swarm/network/messages.go +++ b/swarm/network/messages.go @@ -5,10 +5,11 @@ import ( "net" "time" + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/swarm/network/kademlia" + "github.com/ethereum/go-ethereum/swarm/services/chequebook" "github.com/ethereum/go-ethereum/swarm/services/swap" "github.com/ethereum/go-ethereum/swarm/storage" - "github.com/ethereum/go-ethereum/common/chequebook" - "github.com/ethereum/go-ethereum/common/kademlia" ) /* @@ -108,7 +109,7 @@ type retrieveRequestMsgData struct { MaxSize uint64 // maximum size of delivery accepted MaxPeers uint64 // maximum number of peers returned Timeout uint64 // the longest time we are expecting a response - timeout *time.Time // [not serialied]} + timeout *time.Time // [not serialied] from *peer // } @@ -160,7 +161,9 @@ type peerAddr struct { // peerAddr pretty prints as enode func (self peerAddr) String() string { - return fmt.Sprintf("enode://%x@%v:%d", self.ID, self.IP, self.Port) + var nodeid discover.NodeID + copy(nodeid[:], self.ID) + return discover.NewNode(nodeid, self.IP, 0, self.Port).String() } /* @@ -231,7 +234,7 @@ peer/protocol instance when the node is registered by hive as online{ */ type syncRequestMsgData struct { - SyncState *syncState `rlp:"nil"` + SyncState *syncState `rlp:"nil" ` } func (self *syncRequestMsgData) String() string { diff --git a/swarm/network/protocol.go b/swarm/network/protocol.go index ccf5f119ef..c1eac50ab6 100644 --- a/swarm/network/protocol.go +++ b/swarm/network/protocol.go @@ -1,33 +1,34 @@ package network /* -BZZ implements the bzz wire protocol of swarm +bzz implements the swarm wire protocol [bzz] (sister of eth and shh) the protocol instance is launched on each peer by the network layer if the -BZZ protocol handler is registered on the p2p server. +bzz protocol handler is registered on the p2p server. -The protocol takes care of actually communicating the bzz protocol -* encoding and decoding requests for storage and retrieval -* handling the s§protocol handshake -* dispaching to netstore for handling the DHT logic -* registering peers in the KΛÐΞMLIΛ table via the hive logistic manager -* handling sync protocol messages via the syncer -* talks the SWAP payent protocol (swap accounting is done within NetStore) +The bzz protocol component speaks the bzz protocol +* handle the protocol handshake +* register peers in the KΛÐΞMLIΛ table via the hive logistic manager +* dispatch to hive for handling the DHT logic +* encode and decode requests for storage and retrieval +* handle sync protocol messages via the syncer +* talks the SWAP payment protocol (swap accounting is done within NetStore) */ import ( "fmt" "net" "strconv" + "time" - bzzswap "github.com/ethereum/go-ethereum/swarm/services/swap" - "github.com/ethereum/go-ethereum/swarm/storage" - "github.com/ethereum/go-ethereum/common/chequebook" - "github.com/ethereum/go-ethereum/common/swap" "github.com/ethereum/go-ethereum/errs" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/swarm/services/chequebook" + bzzswap "github.com/ethereum/go-ethereum/swarm/services/swap" + "github.com/ethereum/go-ethereum/swarm/services/swap/swap" + "github.com/ethereum/go-ethereum/swarm/storage" ) const ( @@ -67,7 +68,7 @@ type bzz struct { selfID discover.NodeID // peer's node id used in peer advertising in handshake key storage.Key // baseaddress as storage.Key storage StorageHandler // handler storage/retrieval related requests coming via the bzz wire protocol - hive *Hive // the logistic manager, peerPool, routing servicec and peer handler + hive *Hive // the logistic manager, peerPool, routing service and peer handler dbAccess *DbAccess // access to db storage counter and iterator for syncing requestDb *storage.LDBDatabase // db to persist backlog of deliveries to aid syncing remoteAddr *peerAddr // remote peers address @@ -77,11 +78,11 @@ type bzz struct { swap *swap.Swap // swap instance for the peer connection swapParams *bzzswap.SwapParams // swap settings both local and remote - swapEnabled bool // flag to switch off SWAP (will be via Caps in handshake) + swapEnabled bool // flag to enable SWAP (will be set via Caps in handshake) + syncEnabled bool // flag to enable SYNC (will be set via Caps in handshake) syncer *syncer // syncer instance for the peer connection syncParams *SyncParams // syncer params syncState *syncState // outgoing syncronisation state (contains reference to remote peers db counter) - syncEnabled bool // flag to enable syncing } // interface type for handler of storage/retrieval related requests coming @@ -151,7 +152,7 @@ func run(requestDb *storage.LDBDatabase, depo StorageHandler, hive *Hive, dbacce }, swapParams: sp, syncParams: sy, - swapEnabled: true, + swapEnabled: hive.swapEnabled, syncEnabled: true, } @@ -174,6 +175,10 @@ func run(requestDb *storage.LDBDatabase, depo StorageHandler, hive *Hive, dbacce // the main forever loop that handles incoming requests for { + if self.hive.blockRead { + time.Sleep(1 * time.Second) + continue + } err = self.handle() if err != nil { return @@ -268,8 +273,6 @@ func (self *bzz) handle() error { if err != nil { return self.protoError(ErrDecode, "->msg %v: %v", msg, err) } - // set peers state to persist - self.syncState = req.State case deliveryRequestMsg: // response to syncKeysMsg hashes filtered not existing in db @@ -286,12 +289,14 @@ func (self *bzz) handle() error { case paymentMsg: // swap protocol message for payment, Units paid for, Cheque paid with - var req paymentMsgData - if err := msg.Decode(&req); err != nil { - return self.protoError(ErrDecode, "->msg %v: %v", msg, err) + if self.swapEnabled { + var req paymentMsgData + if err := msg.Decode(&req); err != nil { + return self.protoError(ErrDecode, "->msg %v: %v", msg, err) + } + glog.V(logger.Debug).Infof("[BZZ] incoming payment: %s", req.String()) + self.swap.Receive(int(req.Units), req.Promise) } - glog.V(logger.Debug).Infof("[BZZ] incoming payment: %s", req.String()) - self.swap.Receive(int(req.Units), req.Promise) default: // no other message is allowed @@ -361,10 +366,9 @@ func (self *bzz) handleStatus() (err error) { self.hive.addPeer(&peer{bzz: self}) // hive sets syncstate so sync should start after node added - if self.syncEnabled { - glog.V(logger.Info).Infof("[BZZ] syncronisation request sent with %v", self.syncState) - self.syncRequest() - } + glog.V(logger.Info).Infof("[BZZ] syncronisation request sent with %v", self.syncState) + self.syncRequest() + return nil } @@ -377,9 +381,12 @@ func (self *bzz) sync(state *syncState) error { cnt := self.dbAccess.counter() remoteaddr := self.remoteAddr.Addr start, stop := self.hive.kad.KeyRange(remoteaddr) + + // an explicitly received nil syncstate disables syncronisation if state == nil { - state = newSyncState(start, stop, cnt) - glog.V(logger.Warn).Infof("[BZZ] peer %08x provided no sync state, setting up full sync: %v\n", remoteaddr[:4], state) + self.syncEnabled = false + glog.V(logger.Info).Infof("[BZZ] syncronisation disabled for peer %v", self) + state = &syncState{DbSyncState: &storage.DbSyncState{}, Synced: true} } else { state.synced = make(chan bool) state.SessionAt = cnt @@ -387,6 +394,7 @@ func (self *bzz) sync(state *syncState) error { state.Start = storage.Key(start[:]) state.Stop = storage.Key(stop[:]) } + glog.V(logger.Detail).Infof("[BZZ] syncronisation requested by peer %v at state %v", self, state) } var err error self.syncer, err = newSyncer( @@ -394,11 +402,12 @@ func (self *bzz) sync(state *syncState) error { storage.Key(remoteaddr[:]), self.dbAccess, self.unsyncedKeys, self.store, - self.syncParams, state, + self.syncParams, state, func() bool { return self.syncEnabled }, ) if err != nil { return self.protoError(ErrSync, "%v", err) } + glog.V(logger.Detail).Infof("[BZZ] syncer set for peer %v", self) return nil } @@ -443,8 +452,13 @@ func (self *bzz) store(req *storeRequestMsgData) error { } func (self *bzz) syncRequest() error { - req := &syncRequestMsgData{ - SyncState: self.syncState, + req := &syncRequestMsgData{} + if self.hive.syncEnabled { + glog.V(logger.Detail).Infof("[BZZ] syncronisation request to peer %v at state %v", self, self.syncState) + req.SyncState = self.syncState + } + if self.syncState == nil { + glog.V(logger.Detail).Infof("[BZZ] syncronisation disabled for peer %v at state %v", self, self.syncState) } return self.send(syncRequestMsg, req) } @@ -496,6 +510,9 @@ func (self *bzz) protoErrorDisconnect(err *errs.Error) { } func (self *bzz) send(msg uint64, data interface{}) error { + if self.hive.blockWrite { + return fmt.Errorf("network write blocked") + } glog.V(logger.Debug).Infof("[BZZ] -> %v: %v (%T) to %v", msg, data, data, self) err := p2p.Send(self.rw, msg, data) if err != nil { diff --git a/swarm/network/syncer.go b/swarm/network/syncer.go index 70c7e433ab..9a117be683 100644 --- a/swarm/network/syncer.go +++ b/swarm/network/syncer.go @@ -6,7 +6,6 @@ import ( "fmt" "path/filepath" - "github.com/ethereum/go-ethereum/common/kademlia" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/swarm/storage" @@ -133,6 +132,7 @@ func NewSyncParams(bzzdir string) *SyncParams { // syncer is the agent that manages content distribution/storage replication/chunk storeRequest forwarding type syncer struct { *SyncParams // sync parameters + syncF func() bool // if syncing is needed key storage.Key // remote peers address key state *syncState // sync state for our dbStore syncStates chan *syncState // different stages of sync @@ -165,6 +165,7 @@ func newSyncer( store func(*storeRequestMsgData) error, params *SyncParams, state *syncState, + syncF func() bool, ) (*syncer, error) { syncBufferSize := params.SyncBufferSize @@ -172,6 +173,7 @@ func newSyncer( dbBatchSize := params.RequestDbBatchSize self := &syncer{ + syncF: syncF, key: remotekey, dbAccess: dbAccess, syncStates: make(chan *syncState, 20), @@ -191,35 +193,19 @@ func newSyncer( // initialise a syncdb instance for each priority queue self.queues[i] = newSyncDb(db, remotekey, uint(i), syncBufferSize, dbBatchSize, self.deliver(uint(i))) } - self.state = state glog.V(logger.Info).Infof("[BZZ] syncer started: %v", state) // launch chunk delivery service go self.syncDeliveries() // launch sync task manager - go self.sync() + if self.syncF() { + go self.sync() + } // process unsynced keys to broadcast go self.syncUnsyncedKeys() return self, nil } -// newSyncState returns a default sync state given local and remote -// addresses and db count -func newSyncState(start, stop kademlia.Address, count uint64) *syncState { - // -> (] keyrange for db iterator -- interval open from the left - // storage count range --- interval open from the right - return &syncState{ - DbSyncState: &storage.DbSyncState{ - Start: storage.Key(start[:]), - Stop: storage.Key(stop[:]), - First: 0, - Last: count, - }, - SessionAt: count, - synced: make(chan bool), - } -} - // metadata serialisation func encodeSync(state *syncState) (*json.RawMessage, error) { data, err := json.MarshalIndent(state, "", " ") @@ -238,7 +224,7 @@ func decodeSync(meta *json.RawMessage) (*syncState, error) { if len(data) == 0 { return nil, fmt.Errorf("unable to deserialise sync state from ") } - state := newSyncState(kademlia.Address{}, kademlia.Address{}, 0) + state := &syncState{DbSyncState: &storage.DbSyncState{}} err := json.Unmarshal(data, state) return state, err } @@ -478,6 +464,7 @@ LOOP: glog.V(logger.Warn).Infof("[BZZ] syncer[%v]: unable to send unsynced keys: %v", err) } unsynced = nil + keys = nil } // process item and add it to the batch @@ -503,11 +490,11 @@ LOOP: deliveryRequest = nil case <-newUnsyncedKeys: - glog.V(logger.Detail).Infof("[BZZ] syncer[%v]: new unsynked keys available", self.key.Log()) + glog.V(logger.Detail).Infof("[BZZ] syncer[%v]: new unsynced keys available", self.key.Log()) // this 1 cap channel can wake up the loop // signals that data is available to send if peer is ready to receive newUnsyncedKeys = nil - // this can only happen if keys is + keys = self.keys[High] case state, more = <-syncStates: // this resets the state @@ -620,13 +607,16 @@ func (self *syncer) syncDeliveries() { If sync mode is off then, requests are directly sent to deliveries */ func (self *syncer) addRequest(req interface{}, ty int) { - // retrieve priority for request type + // retrieve priority for request type name int8 + priority := self.SyncPriorities[ty] // sync mode for this type ON - if self.SyncModes[ty] { - self.addKey(req, priority, self.quit) - } else { - self.addDelivery(req, priority, self.quit) + if self.syncF() || ty == DeliverReq { + if self.SyncModes[ty] { + self.addKey(req, priority, self.quit) + } else { + self.addDelivery(req, priority, self.quit) + } } } diff --git a/swarm/services/chequebook/api.go b/swarm/services/chequebook/api.go new file mode 100644 index 0000000000..ee53bb0749 --- /dev/null +++ b/swarm/services/chequebook/api.go @@ -0,0 +1,52 @@ +package chequebook + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +const Version = "1.0" + +var errNoChequebook = errors.New("no chequebook") + +type Api struct { + chequebookf func() *Chequebook +} + +func NewApi(ch func() *Chequebook) *Api { + return &Api{ch} +} + +func (self *Api) Balance() (string, error) { + ch := self.chequebookf() + if ch == nil { + return "", errNoChequebook + } + return ch.Balance().String(), nil +} + +func (self *Api) Issue(beneficiary common.Address, amount *big.Int) (cheque *Cheque, err error) { + ch := self.chequebookf() + if ch == nil { + return nil, errNoChequebook + } + return ch.Issue(beneficiary, amount) +} + +func (self *Api) Cash(cheque *Cheque) (txhash string, err error) { + ch := self.chequebookf() + if ch == nil { + return "", errNoChequebook + } + return ch.Cash(cheque) +} + +func (self *Api) Deposit(amount *big.Int) (txhash string, err error) { + ch := self.chequebookf() + if ch == nil { + return "", errNoChequebook + } + return ch.Deposit(amount) +} diff --git a/common/chequebook/cheque.go b/swarm/services/chequebook/cheque.go similarity index 66% rename from common/chequebook/cheque.go rename to swarm/services/chequebook/cheque.go index 41c89418b0..eb2150b468 100644 --- a/common/chequebook/cheque.go +++ b/swarm/services/chequebook/cheque.go @@ -1,3 +1,4 @@ +//go:generate abigen --sol contract/chequebook.sol --pkg contract --out contract/jaak.go package chequebook import ( @@ -11,14 +12,20 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/swap" - "github.com/ethereum/go-ethereum/core/types" "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/services/chequebook/contract" + "github.com/ethereum/go-ethereum/swarm/services/swap/swap" ) +// func init() { +// glog.SetToStderr(true) +// glog.SetV(6) +// } + /* Chequebook package is a go API to the 'chequebook' ethereum smart contract With convenience methods that allow using chequebook for @@ -38,24 +45,11 @@ Some functionality require interacting with the blockchain: Backend is the interface for that */ -const ( - gasToCash = "2000000" // gas cost of a cash transaction using chequebook - getSentAbiPre = "d75d691d" // sent amount accessor in the chequebook contract - cashAbiPre = "fbf788d6" // abi preamble signature for cash method of the chequebook - queryInterval = 15000000000 // 15 seconds - deployGas = "3000000" - // confirmationInterval = 3 * 10 * *11 // 5 minutes +var ( + gasToCash = big.NewInt(2000000) // gas cost of a cash transaction using chequebook + // gasToDeploy = big.NewInt(3000000) ) -// Backend is the interface to interact with the Ethereum blockchain -// implemented by xeth.XEth -type Backend interface { - Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) - Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error) - GetTxReceipt(txhash common.Hash) *types.Receipt - CodeAt(address string) string -} - // rlp serialised cheque model for use with the chequebook type Cheque struct { // the address of the contract itself needed to avoid cross-contract submission @@ -65,113 +59,84 @@ type Cheque struct { Sig []byte // signature Sign(Sha3(contract, beneficiary, amount), prvKey) } -type Params struct { - ContractCode, ContractAbi, ContractSource string -} - -var ContractParams = &Params{ContractCode, ContractAbi, ContractSource} - func (self *Cheque) String() string { return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", self.Contract.Hex(), self.Beneficiary.Hex(), self.Amount, self.Sig) } +type Params struct { + ContractCode, ContractAbi string +} + +var ContractParams = &Params{contract.ChequebookBin, contract.ChequebookABI} + // chequebook to create, sign cheques from single contract to multiple beneficiarys // outgoing payment handler for peer to peer micropayments type Chequebook struct { path string // path to chequebook file prvKey *ecdsa.PrivateKey // private key to sign cheque with lock sync.Mutex // - backend Backend // blockchain API - quit chan bool // when closed causes autodeposit to stop - owner common.Address // owner address (derived from pubkey) + backend bind.Backend // blockchain API + // abigen bind.ContractBackend // blockchain contract backend for abigen + quit chan bool // when closed causes autodeposit to stop + owner common.Address // owner address (derived from pubkey) + contract *contract.Chequebook // abigen binding + session *contract.ChequebookSession // abigen binding with Tx Opts // persisted fields - balance *big.Int // not synced with blockchain - contract common.Address // contract address - sent map[common.Address]*big.Int //tallies for beneficiarys + balance *big.Int // not synced with blockchain + contractAddr common.Address // contract address + sent map[common.Address]*big.Int //tallies for beneficiarys txhash string // tx hash of last deposit tx threshold *big.Int // threshold that triggers autodeposit if not nil buffer *big.Int // buffer to keep on top of balance for fork protection } -func Deploy(owner common.Address, backend Backend, amount *big.Int, confirmationInterval, timeout time.Duration) (contract common.Address, err error) { - if (owner == common.Address{}) { - return contract, fmt.Errorf("invalid owner %v. Owner needed to deploy chequebook contract: %v", owner.Hex(), err) - } - - txhash, err := backend.Transact(owner.Hex(), "", "", amount.String(), deployGas, "", ContractCode) - if err != nil { - return contract, fmt.Errorf("unable to send chequebook creation transaction: %v", err) - } - - timer := time.NewTimer(timeout).C - ticker := time.NewTicker(queryInterval).C -OUT: - for { - select { - - case <-timer: - if ticker == nil { - // ticker is nil, receipt was found and confirmation interval passed - err = Validate(contract, backend) - if err != nil { - return contract, fmt.Errorf("invalid contract at %v after %v: %v", contract.Hex(), confirmationInterval, err) - } - break OUT - } - // ticker is non-nil meaning receipt not found yet - return contract, fmt.Errorf("chequebook deployment timed out in %v", timeout) - - case <-ticker: - receipt := backend.GetTxReceipt(common.HexToHash(txhash)) - if receipt != nil { - contract = receipt.ContractAddress - glog.V(logger.Detail).Infof("[CHEQUEBOOK] chequebook deployed at %v (owner: %v)", contract.Hex(), owner.Hex()) - timer = time.NewTimer(confirmationInterval).C - ticker = nil - } else { - glog.V(logger.Detail).Infof("[CHEQUEBOOK] check if chequebook deployed (txhash: %v)", txhash) - } - - } - } - return contract, nil +func (self *Chequebook) String() string { + return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", self.contractAddr.Hex(), self.owner.Hex(), self.balance, self.prvKey.PublicKey) } -func Validate(contract common.Address, backend Backend) (err error) { - if (contract == common.Address{}) { - return fmt.Errorf("zero address") - } - code := backend.CodeAt(contract.Hex()) - if code != ContractDeployedCode { - return fmt.Errorf("incorrect code %v:\n%v\n%v", contract.Hex(), code, ContractDeployedCode) - } - return nil -} - -// NewChequebook(path, contract, balance, prvKey) creates a new Chequebook -func NewChequebook(path string, contract common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) { +// NewChequebook(path, contract, prvKey, abibbackend, backend) creates a new Chequebook +func NewChequebook(path string, contractAddr common.Address, prvKey *ecdsa.PrivateKey, backend bind.Backend) (self *Chequebook, err error) { balance := new(big.Int) sent := make(map[common.Address]*big.Int) - owner := crypto.PubkeyToAddress(prvKey.PublicKey) - self = &Chequebook{ - balance: balance, - contract: contract, - sent: sent, - path: path, - prvKey: prvKey, - backend: backend, - owner: owner, + + chbook, err := contract.NewChequebook(contractAddr, backend) + if err != nil { + return nil, err } - if (contract != common.Address{}) { - glog.V(logger.Detail).Infof("[CHEQUEBOOK] new chequebook initialised from %v (owner: %v)", contract.Hex(), owner.Hex()) + transactOpts := bind.NewKeyedTransactor(prvKey) + session := &contract.ChequebookSession{ + Contract: chbook, + TransactOpts: *transactOpts, + } + + self = &Chequebook{ + prvKey: prvKey, + balance: balance, + contractAddr: contractAddr, + sent: sent, + path: path, + backend: backend, + owner: transactOpts.From, + contract: chbook, + session: session, + } + + if (contractAddr != common.Address{}) { + self.setBalanceFromBlockChain() + glog.V(logger.Detail).Infof("[CHEQUEBOOK] new chequebook initialised from %v (owner: %v, balance: %s)", contractAddr, self.owner.Hex(), self.balance.String()) } return } +func (self *Chequebook) setBalanceFromBlockChain() { + balance := self.backend.BalanceAt(self.contractAddr) + self.balance.Set(balance) +} + // LoadChequebook(path, prvKey, backend) loads a chequebook from disk (file path) -func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) { +func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend bind.Backend, checkBalance bool) (self *Chequebook, err error) { var data []byte data, err = ioutil.ReadFile(path) if err != nil { @@ -184,7 +149,11 @@ func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend) (sel if err != nil { return nil, err } - glog.V(logger.Detail).Infof("[CHEQUEBOOK] loaded chequebook (%s, owner: %v) initialised from %v", self.contract.Hex(), self.owner.Hex(), path) + if checkBalance { + self.setBalanceFromBlockChain() + } + + glog.V(logger.Detail).Infof("[CHEQUEBOOK] loaded chequebook (%s, owner: %v, balance: %v) initialised from %v", self.contractAddr.Hex(), self.owner.Hex(), self.balance, path) return } @@ -207,7 +176,7 @@ func (self *Chequebook) UnmarshalJSON(data []byte) error { if !ok { return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance) } - self.contract = common.HexToAddress(file.Contract) + self.contractAddr = common.HexToAddress(file.Contract) for addr, sent := range file.Sent { self.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10) if !ok { @@ -220,7 +189,7 @@ func (self *Chequebook) UnmarshalJSON(data []byte) error { func (self *Chequebook) MarshalJSON() ([]byte, error) { var file = &chequebookFile{ Balance: self.balance.String(), - Contract: self.contract.Hex(), + Contract: self.contractAddr.Hex(), Owner: self.owner.Hex(), Sent: make(map[string]string), } @@ -238,7 +207,7 @@ func (self *Chequebook) Save() (err error) { if err != nil { return err } - glog.V(logger.Detail).Infof("[CHEQUEBOOK] saving chequebook (%s) to %v", self.contract.Hex(), self.path) + glog.V(logger.Detail).Infof("[CHEQUEBOOK] saving chequebook (%s) to %v", self.contractAddr.Hex(), self.path) return ioutil.WriteFile(self.path, data, os.ModePerm) } @@ -259,6 +228,7 @@ func (self *Chequebook) Stop() { func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch *Cheque, err error) { defer self.lock.Unlock() self.lock.Lock() + glog.V(logger.Detail).Infof("[CHEQUEBOOK] prvKey: %v", self.prvKey) if amount.Cmp(common.Big0) <= 0 { return nil, fmt.Errorf("amount must be greater than zero (%v)", amount) } @@ -273,10 +243,11 @@ func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch * } sum := new(big.Int).Set(sent) sum.Add(sum, amount) - sig, err = crypto.Sign(sigHash(self.contract, beneficiary, sum), self.prvKey) + + sig, err = crypto.Sign(sigHash(self.contractAddr, beneficiary, sum), self.prvKey) if err == nil { ch = &Cheque{ - Contract: self.contract, + Contract: self.contractAddr, Beneficiary: beneficiary, Amount: sum, Sig: sig, @@ -301,7 +272,7 @@ func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch * // convenience method to cash any cheque func (self *Chequebook) Cash(ch *Cheque) (txhash string, err error) { - return ch.Cash(self.owner, self.backend) + return ch.Cash(self.session) } // data to sign: contract address, beneficiary, cumulative amount of funds ever sent @@ -330,13 +301,13 @@ func (self *Chequebook) Owner() common.Address { } // Backend() public accessor for backend -func (self *Chequebook) Backend() Backend { +func (self *Chequebook) Backend() bind.Backend { return self.backend } // Address() public accessor for contract func (self *Chequebook) Address() common.Address { - return self.contract + return self.contractAddr } // Deposit(amount) deposits amount to the chequebook account @@ -349,15 +320,19 @@ func (self *Chequebook) Deposit(amount *big.Int) (string, error) { // deposit(amount) deposits amount to the chequebook account // caller holds the lock func (self *Chequebook) deposit(amount *big.Int) (string, error) { - txhash, err := self.backend.Transact(self.owner.Hex(), self.contract.Hex(), "", amount.String(), "", "", "") + // since the amount is variable here, we do not use sessions + depositTransactor := bind.NewKeyedTransactor(self.prvKey) + depositTransactor.Value = amount + chbookRaw := &contract.ChequebookRaw{self.contract} + tx, err := chbookRaw.Transfer(depositTransactor) // assume that transaction is actually successful, we add the amount to balance right away if err != nil { - glog.V(logger.Warn).Infof("[CHEQUEBOOK] error depositing %d wei to chequebook (%s, balance: %v, target: %v): %v", amount, self.contract.Hex(), self.balance, self.buffer, err) + glog.V(logger.Warn).Infof("[CHEQUEBOOK] error depositing %d wei to chequebook (%s, balance: %v, target: %v): %v", amount, self.contractAddr.Hex(), self.balance, self.buffer, err) } else { self.balance.Add(self.balance, amount) - glog.V(logger.Detail).Infof("[CHEQUEBOOK] deposited %d wei to chequebook (%s, balance: %v, target: %v)", amount, self.contract.Hex(), self.balance, self.buffer) + glog.V(logger.Detail).Infof("[CHEQUEBOOK] deposited %d wei to chequebook (%s, balance: %v, target: %v)", amount, self.contractAddr.Hex(), self.balance, self.buffer) } - return txhash, err + return tx.Hash().Hex(), err } // AutoDeposit(interval, threshold, buffer) (re)sets interval time and amount @@ -435,43 +410,50 @@ func (self *Outbox) String() string { return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", self.chequeBook.Address().Hex(), self.beneficiary.Hex(), self.chequeBook.Balance()) } -// type ChequeQueue struct { -// beneficiary common.Address -// last map[string]*Inbox -// } - // inbox to deposit, verify and cash cheques // from a single contract to single beneficiary // incoming payment handler for peer to peer micropayments type Inbox struct { lock sync.Mutex - contract common.Address // peer's chequebook contract - beneficiary common.Address // local peer's receiving address - sender common.Address // local peer's address to send cashing tx from - signer *ecdsa.PublicKey // peer's public key - txhash string // tx hash of last cashing tx - backend Backend // blockchain API - quit chan bool // when closed causes autocash to stop - maxUncashed *big.Int // threshold that triggers autocashing - cashed *big.Int // cumulative amount cashed - cheque *Cheque // last cheque, nil if none yet received + contract common.Address // peer's chequebook contract + beneficiary common.Address // local peer's receiving address + sender common.Address // local peer's address to send cashing tx from + signer *ecdsa.PublicKey // peer's public key + txhash string // tx hash of last cashing tx + abigen bind.ContractBackend // blockchain API + session *contract.ChequebookSession // abi contract backend with tx opts + quit chan bool // when closed causes autocash to stop + maxUncashed *big.Int // threshold that triggers autocashing + cashed *big.Int // cumulative amount cashed + cheque *Cheque // last cheque, nil if none yet received } // NewInbox(contract, beneficiary, signer, backend) constructor for Inbox // not persisted, cumulative sum updated from blockchain when first cheque received // backend used to sync amount (Call) as well as cash the cheques (Transact) -func NewInbox(contract, sender, beneficiary common.Address, signer *ecdsa.PublicKey, backend Backend) (self *Inbox, err error) { +func NewInbox(prvKey *ecdsa.PrivateKey, contractAddr, beneficiary common.Address, signer *ecdsa.PublicKey, abigen bind.ContractBackend) (self *Inbox, err error) { if signer == nil { return nil, fmt.Errorf("signer is null") } + chbook, err := contract.NewChequebook(contractAddr, abigen) + if err != nil { + return nil, err + } + transactOpts := bind.NewKeyedTransactor(prvKey) + transactOpts.GasLimit = gasToCash + session := &contract.ChequebookSession{ + Contract: chbook, + TransactOpts: *transactOpts, + } + sender := transactOpts.From self = &Inbox{ - contract: contract, + contract: contractAddr, beneficiary: beneficiary, sender: sender, signer: signer, - backend: backend, + session: session, cashed: new(big.Int).Set(common.Big0), } glog.V(logger.Detail).Infof("[CHEQUEBOOK] initialised inbox (%s -> %s) expected signer: %x", self.contract.Hex(), self.beneficiary.Hex(), crypto.FromECDSAPub(signer)) @@ -494,7 +476,7 @@ func (self *Inbox) Stop() { func (self *Inbox) Cash() (txhash string, err error) { if self.cheque != nil { - txhash, err = self.cheque.Cash(self.sender, self.backend) + txhash, err = self.cheque.Cash(self.session) glog.V(logger.Detail).Infof("[CHEQUEBOOK] cashing cheque (total: %v) on chequebook (%s) sending to %v", self.cheque.Amount, self.contract.Hex(), self.beneficiary.Hex()) self.cashed = self.cheque.Amount } @@ -548,7 +530,7 @@ func (self *Inbox) autoCash(cashInterval time.Duration) { return } -// Reveive(cheque) called to deposit latest cheque to incoming Inbox +// Receive(cheque) called to deposit latest cheque to incoming Inbox func (self *Inbox) Receive(promise swap.Promise) (*big.Int, error) { ch := promise.(*Cheque) @@ -557,18 +539,12 @@ func (self *Inbox) Receive(promise swap.Promise) (*big.Int, error) { var sum *big.Int if self.cheque == nil { // the sum is checked against the blockchain once a check is received - tallyhex, _, err := self.backend.Call(self.beneficiary.Hex(), self.contract.Hex(), "", "", "", getSentAbiEncode(ch.Contract)) + // + tally, err := self.session.Sent(self.beneficiary) if err != nil { - return nil, fmt.Errorf("inbox: error calling backend to set amount: %v", err) + return nil, fmt.Errorf("inbox: error calling backend to set amount: %v", err) } - - tally := common.FromHex(tallyhex) - // var ok bool - // sum, ok = new(big.Int).SetString(tally, 10) - // if !ok { - // return nil, fmt.Errorf("inbox: cannot convert amount '%s' (%v) to integer", tallyhex, tally) - // } - sum = new(big.Int).SetBytes(tally) + sum = tally } else { sum = self.cheque.Amount } @@ -590,37 +566,6 @@ func (self *Inbox) Receive(promise swap.Promise) (*big.Int, error) { return amount, err } -// RSV representation of signature -func sig2rsv(sig []byte) (v byte, r, s []byte) { - v = sig[64] + 27 - r = sig[:32] - s = sig[32:64] - return -} - -func getSentAbiEncode(beneficiary common.Address) string { - var beneficiary32 [32]byte - copy(beneficiary32[12:], beneficiary.Bytes()) - return getSentAbiPre + common.Bytes2Hex(beneficiary32[:]) -} - -// abi encoding of a cheque to send as eth tx data -func (self *Cheque) cashAbiEncode() string { - v, r, s := sig2rsv(self.Sig) - // cashAbiPre, beneficiary, amount, v, r, s - bigamount := self.Amount.Bytes() - if len(bigamount) > 32 { - glog.V(logger.Detail).Infof("[CHEQUEBOOK] number too big: %v (>32 bytes)", self.Amount) - return "" - } - var beneficiary32, amount32, vabi [32]byte - copy(beneficiary32[12:], self.Beneficiary.Bytes()) - copy(amount32[32-len(bigamount):32], bigamount) - vabi[31] = v - return cashAbiPre + common.Bytes2Hex(beneficiary32[:]) + common.Bytes2Hex(amount32[:]) + - common.Bytes2Hex(vabi[:]) + common.Bytes2Hex(r) + common.Bytes2Hex(s) -} - // Verify(cheque) verifies cheque for signer, contract, beneficiary, amount, valid signature func (self *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) { glog.V(logger.Detail).Infof("[CHEQUEBOOK] verify cheque: %v - sum: %v", self, sum) @@ -653,8 +598,21 @@ func (self *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary com return amount, nil } -// Cash(backend) will cash the check using xeth backend to send a transaction -// Beneficiary address should be unlocked -func (self *Cheque) Cash(sender common.Address, backend Backend) (string, error) { - return backend.Transact(sender.Hex(), self.Contract.Hex(), "", "", "", gasToCash, self.cashAbiEncode()) +// v/r/s representation of signature +func sig2vrs(sig []byte) (v *big.Int, r, s [32]byte) { + v = big.NewInt(int64(sig[64] + 27)) + copy(r[:], sig[:32]) + copy(s[:], sig[32:64]) + return +} + +// Cash(backend) will cash the check using abi contract backend to send a transaction +// Beneficiary address should be unlocked +func (self *Cheque) Cash(session *contract.ChequebookSession) (string, error) { + v, r, s := sig2vrs(self.Sig) + tx, err := session.Cash(self.Beneficiary, self.Amount, v, r, s) + if err != nil { + return "", err + } + return tx.Hash().Hex(), nil } diff --git a/common/chequebook/cheque_test.go b/swarm/services/chequebook/cheque_test.go similarity index 52% rename from common/chequebook/cheque_test.go rename to swarm/services/chequebook/cheque_test.go index 06d95ff380..c74b64be43 100644 --- a/common/chequebook/cheque_test.go +++ b/swarm/services/chequebook/cheque_test.go @@ -1,88 +1,103 @@ package chequebook import ( + "crypto/ecdsa" "math/big" "testing" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/swarm/services/chequebook/contract" +) + +var ( + key0, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key1, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key2, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr0 = crypto.PubkeyToAddress(key0.PublicKey) + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) ) type testBackend struct { - calls []string - errs []error - txs []string + *backends.SimulatedBackend +} + +func accounts() []core.GenesisAccount { + + return []core.GenesisAccount{ + core.GenesisAccount{addr0, big.NewInt(1000000000)}, + core.GenesisAccount{addr1, big.NewInt(1000000000)}, + core.GenesisAccount{addr2, big.NewInt(1000000000)}, + } } func newTestBackend() *testBackend { - return &testBackend{} -} - -func (b *testBackend) Transact(fromStr, toStr, nonceStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { - txhash := string(crypto.Sha3([]byte(codeStr))) - b.txs = append(b.txs, txhash) - return txhash, nil -} - -func (b *testBackend) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error) { - if len(b.calls) == 0 { - panic("test backend called too many times") - } - res := b.calls[0] - err := b.errs[0] - b.calls = b.calls[1:] - b.errs = b.errs[1:] - return res, "", err + accs := accounts() + return &testBackend{SimulatedBackend: backends.NewSimulatedBackend(accs...)} } func (b *testBackend) GetTxReceipt(txhash common.Hash) *types.Receipt { return nil } -func (b *testBackend) CodeAt(address string) string { +func (b *testBackend) CodeAt(address common.Address) string { return "" } -func genAddr() common.Address { - prvKey, _ := crypto.GenerateKey() - return crypto.PubkeyToAddress(prvKey.PublicKey) +func (b *testBackend) BalanceAt(address common.Address) *big.Int { + return big.NewInt(0) +} + +func deploy(prvKey *ecdsa.PrivateKey, amount *big.Int, backend *backends.SimulatedBackend) (common.Address, error) { + deployTransactor := bind.NewKeyedTransactor(prvKey) + deployTransactor.Value = amount + addr, _, _, err := contract.DeployChequebook(deployTransactor, backend) + if err != nil { + return common.Address{}, err + } + backend.Commit() + return addr, nil } func TestIssueAndReceive(t *testing.T) { - prvKey, _ := crypto.GenerateKey() - sender := genAddr() path := "/tmp/checkbook.json" - chbook, err := NewChequebook(path, sender, prvKey, nil) + backend := newTestBackend() + addr0, err := deploy(key0, big.NewInt(0), backend.SimulatedBackend) if err != nil { - t.Errorf("expected no error, got %v", err) + t.Fatalf("deploy contract: expected no error, got %v", err) } - recipient := genAddr() - chbook.sent[recipient] = new(big.Int).SetUint64(42) + chbook, err := NewChequebook(path, addr0, key0, backend) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + chbook.sent[addr1] = new(big.Int).SetUint64(42) amount := common.Big1 - ch, err := chbook.Issue(recipient, amount) + ch, err := chbook.Issue(addr1, amount) if err == nil { - t.Errorf("expected insufficient funds error, got none") + t.Fatalf("expected insufficient funds error, got none") } chbook.balance = new(big.Int).Set(common.Big1) if chbook.Balance().Cmp(common.Big1) != 0 { - t.Errorf("expected: %v, got %v", "0", chbook.Balance()) + t.Fatalf("expected: %v, got %v", "0", chbook.Balance()) } - ch, err = chbook.Issue(recipient, amount) + ch, err = chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } + if chbook.Balance().Cmp(common.Big0) != 0 { t.Errorf("expected: %v, got %v", "0", chbook.Balance()) } - backend := newTestBackend() - backend.calls = []string{common.ToHex(big.NewInt(42).Bytes())} - backend.errs = []error{nil} - chbox, err := NewInbox(sender, recipient, recipient, &prvKey.PublicKey, backend) + chbox, err := NewInbox(key1, addr0, addr1, &key0.PublicKey, backend) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -92,27 +107,25 @@ func TestIssueAndReceive(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - if received.Cmp(common.Big1) != 0 { - t.Errorf("expected: %v, got %v", "1", received) + if received.Cmp(big.NewInt(43)) != 0 { + t.Errorf("expected: %v, got %v", "43", received) } } func TestCheckbookFile(t *testing.T) { - prvKey, _ := crypto.GenerateKey() - sender := genAddr() path := "/tmp/checkbook.json" - chbook, err := NewChequebook(path, sender, prvKey, nil) + backend := newTestBackend() + chbook, err := NewChequebook(path, addr0, key0, backend) if err != nil { - t.Errorf("expected no error, got %v", err) + t.Fatalf("expected no error, got %v", err) } - recipient := genAddr() - chbook.sent[recipient] = new(big.Int).SetUint64(42) + chbook.sent[addr1] = new(big.Int).SetUint64(42) chbook.balance = new(big.Int).Set(common.Big1) chbook.Save() - chbook, err = LoadChequebook(path, prvKey, nil) + chbook, err = LoadChequebook(path, key0, backend, false) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -120,7 +133,7 @@ func TestCheckbookFile(t *testing.T) { t.Errorf("expected: %v, got %v", "0", chbook.Balance()) } - ch, err := chbook.Issue(recipient, common.Big1) + ch, err := chbook.Issue(addr1, common.Big1) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -135,31 +148,35 @@ func TestCheckbookFile(t *testing.T) { } func TestVerifyErrors(t *testing.T) { - prvKey, _ := crypto.GenerateKey() - sender0 := genAddr() - sender1 := genAddr() path0 := "/tmp/checkbook0.json" - chbook0, err := NewChequebook(path0, sender0, prvKey, nil) + backend := newTestBackend() + contr0, err := deploy(key0, common.Big2, backend.SimulatedBackend) if err != nil { t.Errorf("expected no error, got %v", err) } + chbook0, err := NewChequebook(path0, contr0, key0, backend) + if err != nil { + t.Errorf("expected no error, got %v", err) + } + path1 := "/tmp/checkbook1.json" - chbook1, err := NewChequebook(path1, sender1, prvKey, nil) + contr1, err := deploy(key1, common.Big2, backend.SimulatedBackend) + chbook1, err := NewChequebook(path1, contr1, key1, backend) if err != nil { t.Errorf("expected no error, got %v", err) } - recipient0 := genAddr() - recipient1 := genAddr() + + chbook0.sent[addr1] = new(big.Int).SetUint64(42) chbook0.balance = new(big.Int).Set(common.Big2) chbook1.balance = new(big.Int).Set(common.Big1) - chbook0.sent[recipient0] = new(big.Int).SetUint64(42) amount := common.Big1 - ch0, err := chbook0.Issue(recipient0, amount) + ch0, err := chbook0.Issue(addr1, amount) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } - backend := newTestBackend() - backend.calls = []string{common.ToHex(big.NewInt(42).Bytes())} - backend.errs = []error{nil} - chbox, err := NewInbox(sender0, recipient0, recipient0, &prvKey.PublicKey, backend) + time.Sleep(5) + chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -169,39 +186,39 @@ func TestVerifyErrors(t *testing.T) { t.Fatalf("expected no error, got %v", err) } - if received.Cmp(common.Big1) != 0 { - t.Errorf("expected: %v, got %v", "1", received) + if received.Cmp(big.NewInt(43)) != 0 { + t.Errorf("expected: %v, got %v", "43", received) } - ch1, err := chbook0.Issue(recipient1, amount) + ch1, err := chbook0.Issue(addr2, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } received, err = chbox.Receive(ch1) - t.Log(err) + t.Logf("correct error: %v", err) if err == nil { t.Fatalf("expected receiver error, got none") } - ch2, err := chbook1.Issue(recipient0, amount) + ch2, err := chbook1.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } received, err = chbox.Receive(ch2) - t.Log(err) + t.Logf("correct error: %v", err) if err == nil { t.Fatalf("expected sender error, got none") } - _, err = chbook1.Issue(recipient0, new(big.Int).SetInt64(-1)) - t.Log(err) + _, err = chbook1.Issue(addr1, new(big.Int).SetInt64(-1)) + t.Logf("correct error: %v", err) if err == nil { t.Fatalf("expected incorrect amount error, got none") } received, err = chbox.Receive(ch0) - t.Log(err) + t.Logf("correct error: %v", err) if err == nil { t.Fatalf("expected incorrect amount error, got none") } @@ -209,31 +226,27 @@ func TestVerifyErrors(t *testing.T) { } func TestDeposit(t *testing.T) { - prvKey, _ := crypto.GenerateKey() - sender := genAddr() - path := "/tmp/checkbook.json" - + path0 := "/tmp/checkbook0.json" backend := newTestBackend() - chbook, err := NewChequebook(path, sender, prvKey, backend) + contr0, err := deploy(key0, common.Big2, backend.SimulatedBackend) + chbook, err := NewChequebook(path0, contr0, key0, backend) if err != nil { t.Errorf("expected no error, got %v", err) } balance := new(big.Int).SetUint64(42) chbook.Deposit(balance) - if len(backend.txs) != 1 { - t.Fatalf("expected 1 txs to send, got %v", len(backend.txs)) - } + backend.Commit() if chbook.balance.Cmp(balance) != 0 { t.Fatalf("expected balance %v, got %v", balance, chbook.balance) } - recipient := genAddr() amount := common.Big1 - _, err = chbook.Issue(recipient, amount) + _, err = chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } + backend.Commit() exp := new(big.Int).SetUint64(41) if chbook.balance.Cmp(exp) != 0 { t.Fatalf("expected balance %v, got %v", exp, chbook.balance) @@ -241,36 +254,33 @@ func TestDeposit(t *testing.T) { // autodeposit on each issue chbook.AutoDeposit(0, balance, balance) - _, err = chbook.Issue(recipient, amount) + _, err = chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } - _, err = chbook.Issue(recipient, amount) + backend.Commit() + _, err = chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } - - if len(backend.txs) != 3 { - t.Fatalf("expected 3 txs to send, got %v", len(backend.txs)) - } + backend.Commit() if chbook.balance.Cmp(balance) != 0 { t.Fatalf("expected balance %v, got %v", balance, chbook.balance) } // autodeposit off chbook.AutoDeposit(0, common.Big0, balance) - _, err = chbook.Issue(recipient, amount) + _, err = chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } - _, err = chbook.Issue(recipient, amount) + backend.Commit() + _, err = chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } + backend.Commit() - if len(backend.txs) != 3 { - t.Fatalf("expected 3 txs to send, got %v", len(backend.txs)) - } exp = new(big.Int).SetUint64(40) if chbook.balance.Cmp(exp) != 0 { t.Fatalf("expected balance %v, got %v", exp, chbook.balance) @@ -279,59 +289,52 @@ func TestDeposit(t *testing.T) { // autodeposit every 10ms if new cheque issued interval := 30 * time.Millisecond chbook.AutoDeposit(interval, common.Big1, balance) - _, err = chbook.Issue(recipient, amount) + _, err = chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } - _, err = chbook.Issue(recipient, amount) + backend.Commit() + _, err = chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } + backend.Commit() - if len(backend.txs) != 3 { - t.Fatalf("expected 3 txs to send, got %v", len(backend.txs)) - } exp = new(big.Int).SetUint64(38) if chbook.balance.Cmp(exp) != 0 { t.Fatalf("expected balance %v, got %v", exp, chbook.balance) } time.Sleep(3 * interval) - if len(backend.txs) != 4 { - t.Fatalf("expected 4 txs to send, got %v", len(backend.txs)) - } + backend.Commit() if chbook.balance.Cmp(balance) != 0 { t.Fatalf("expected balance %v, got %v", balance, chbook.balance) } exp = new(big.Int).SetUint64(40) chbook.AutoDeposit(4*interval, exp, balance) - _, err = chbook.Issue(recipient, amount) + _, err = chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } - _, err = chbook.Issue(recipient, amount) + backend.Commit() + _, err = chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } - time.Sleep(3 * interval) - if len(backend.txs) != 4 { - t.Fatalf("expected 4 txs to send, got %v", len(backend.txs)) - } + backend.Commit() if chbook.balance.Cmp(exp) != 0 { t.Fatalf("expected balance %v, got %v", exp, chbook.balance) } - _, err = chbook.Issue(recipient, amount) + _, err = chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } time.Sleep(1 * interval) + backend.Commit() - if len(backend.txs) != 5 { - t.Fatalf("expected 5 txs to send, got %v", len(backend.txs)) - } if chbook.balance.Cmp(balance) != 0 { t.Fatalf("expected balance %v, got %v", balance, chbook.balance) } @@ -339,21 +342,20 @@ func TestDeposit(t *testing.T) { chbook.AutoDeposit(1*interval, common.Big0, balance) chbook.Stop() - _, err = chbook.Issue(recipient, common.Big1) + _, err = chbook.Issue(addr1, common.Big1) if err != nil { t.Fatalf("expected no error, got %v", err) } + backend.Commit() - _, err = chbook.Issue(recipient, common.Big2) + _, err = chbook.Issue(addr1, common.Big2) if err != nil { t.Fatalf("expected no error, got %v", err) } time.Sleep(1 * interval) + backend.Commit() - if len(backend.txs) != 5 { - t.Fatalf("expected 5 txs to send, got %v", len(backend.txs)) - } exp = new(big.Int).SetUint64(39) if chbook.balance.Cmp(exp) != 0 { t.Fatalf("expected balance %v, got %v", exp, chbook.balance) @@ -362,26 +364,22 @@ func TestDeposit(t *testing.T) { } func TestCash(t *testing.T) { - prvKey, _ := crypto.GenerateKey() - sender := genAddr() path := "/tmp/checkbook.json" - chbook, err := NewChequebook(path, sender, prvKey, nil) + backend := newTestBackend() + contr0, err := deploy(key0, common.Big2, backend.SimulatedBackend) + chbook, err := NewChequebook(path, contr0, key0, backend) if err != nil { t.Errorf("expected no error, got %v", err) } - recipient := genAddr() - chbook.sent[recipient] = new(big.Int).SetUint64(42) + chbook.sent[addr1] = new(big.Int).SetUint64(42) amount := common.Big1 chbook.balance = new(big.Int).Set(common.Big1) - ch, err := chbook.Issue(recipient, amount) + ch, err := chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } - - backend := newTestBackend() - backend.calls = []string{common.ToHex(big.NewInt(42).Bytes())} - backend.errs = []error{nil} - chbox, err := NewInbox(sender, recipient, recipient, &prvKey.PublicKey, backend) + backend.Commit() + chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend) if err != nil { t.Fatalf("expected no error, got %v", err) } @@ -391,20 +389,20 @@ func TestCash(t *testing.T) { if err != nil { t.Fatalf("expected no error, got %v", err) } - _, err = ch.Cash(recipient, backend) - if len(backend.txs) != 1 { - t.Fatalf("expected 1 txs to send, got %v", len(backend.txs)) - } + _, err = ch.Cash(chbook.session) + backend.Commit() chbook.balance = new(big.Int).Set(common.Big3) - ch0, err := chbook.Issue(recipient, amount) + ch0, err := chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } - ch1, err := chbook.Issue(recipient, amount) + backend.Commit() + ch1, err := chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } + backend.Commit() interval := 10 * time.Millisecond // setting autocash with interval of 10ms @@ -417,82 +415,119 @@ func TestCash(t *testing.T) { if err != nil { t.Fatalf("expected no error, got %v", err) } - // after < interval time and 2 cheques received, no new cashing tx is sent - if len(backend.txs) != 1 { - t.Fatalf("expected 1 txs to send, got %v", len(backend.txs)) - } + backend.Commit() + // expBalance := big.NewInt(2) + // gotBalance := backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } // after 3x interval time and 2 cheques received, exactly one cashing tx is sent time.Sleep(4 * interval) - if len(backend.txs) != 2 { - t.Fatalf("expected 2 txs to send, got %v", len(backend.txs)) - } + backend.Commit() + + // expBalance = big.NewInt(4) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } // after stopping autocash no more tx are sent - ch2, err := chbook.Issue(recipient, amount) + ch2, err := chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } chbox.Stop() - time.Sleep(interval) // make sure loop stops _, err = chbox.Receive(ch2) if err != nil { t.Fatalf("expected no error, got %v", err) } time.Sleep(2 * interval) - if len(backend.txs) != 2 { - t.Fatalf("expected 2 txs to send, got %v", len(backend.txs)) - } + backend.Commit() + // expBalance = big.NewInt(4) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } // autocash below 1 - chbook.balance = new(big.Int).Set(common.Big2) + chbook.balance = big.NewInt(2) chbox.AutoCash(0, common.Big1) - ch3, err := chbook.Issue(recipient, amount) + ch3, err := chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } - ch4, err := chbook.Issue(recipient, amount) + backend.Commit() + // expBalance = big.NewInt(4) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } + + ch4, err := chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } + backend.Commit() _, err = chbox.Receive(ch3) if err != nil { t.Fatalf("expected no error, got %v", err) } + backend.Commit() _, err = chbox.Receive(ch4) if err != nil { t.Fatalf("expected no error, got %v", err) } + backend.Commit() // 2 checks of amount 1 received, exactly 1 tx is sent - if len(backend.txs) != 3 { - t.Fatalf("expected 3 txs to send, got %v", len(backend.txs)) - } + // expBalance = big.NewInt(6) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } // autochash on receipt when maxUncashed is 0 chbook.balance = new(big.Int).Set(common.Big2) chbox.AutoCash(0, common.Big0) - ch5, err := chbook.Issue(recipient, amount) + ch5, err := chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } - ch6, err := chbook.Issue(recipient, amount) + backend.Commit() + // expBalance = big.NewInt(5) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } + + ch6, err := chbook.Issue(addr1, amount) if err != nil { t.Fatalf("expected no error, got %v", err) } + _, err = chbox.Receive(ch5) if err != nil { t.Fatalf("expected no error, got %v", err) } + backend.Commit() + // expBalance = big.NewInt(4) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } + _, err = chbox.Receive(ch6) if err != nil { t.Fatalf("expected no error, got %v", err) } - - if len(backend.txs) != 5 { - t.Fatalf("expected 5 txs to send, got %v", len(backend.txs)) - } + backend.Commit() + // expBalance = big.NewInt(6) + // gotBalance = backend.BalanceAt(addr1) + // if gotBalance.Cmp(expBalance) != 0 { + // t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance) + // } } diff --git a/swarm/services/chequebook/contract/chequebook.go b/swarm/services/chequebook/contract/chequebook.go new file mode 100644 index 0000000000..4b12e13b83 --- /dev/null +++ b/swarm/services/chequebook/contract/chequebook.go @@ -0,0 +1,542 @@ +// This file is an automatically generated Go binding. Do not modify as any +// change will likely be lost upon the next re-generation! + +package contract + +import ( + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// ChequebookABI is the input ABI used to generate the binding from. +const ChequebookABI = `[{"constant":false,"inputs":[],"name":"kill","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"sent","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"}]` + +// ChequebookBin is the compiled bytecode used for deploying new contracts. +const ChequebookBin = `0x606060405260008054600160a060020a031916331790556101fe806100246000396000f3606060405260e060020a600035046341c0e1b581146100315780637bf786f814610059578063fbf788d614610071575b005b61002f60005433600160a060020a03908116911614156101fc57600054600160a060020a0316ff5b6100ab60043560016020526000908152604090205481565b61002f600435602435604435606435608435600160a060020a03851660009081526001602052604081205485116100b5575b505050505050565b6060908152602090f35b30600160a060020a039081166c0100000000000000000000000090810260609081529188160260745260888690526048812080825260ff8616608090815260a086905260c0859052909260019260e0926020928290866161da5a03f11561000257505060405151600054600160a060020a03908116911614610136576100a3565b85600160a060020a031660006001600050600089600160a060020a03168152602001908152602001600020600050548703604051809050600060405180830381858888f19350505050156101b057846001600050600088600160a060020a03168152602001908152602001600020600050819055506100a3565b60005460408051600160a060020a03929092168252517f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f9789181900360200190a185600160a060020a0316ff5b56` + +// DeployChequebook deploys a new Ethereum contract, binding an instance of Chequebook to it. +func DeployChequebook(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Chequebook, error) { + parsed, err := abi.JSON(strings.NewReader(ChequebookABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ChequebookBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}}, nil +} + +// Chequebook is an auto generated Go binding around an Ethereum contract. +type Chequebook struct { + ChequebookCaller // Read-only binding to the contract + ChequebookTransactor // Write-only binding to the contract +} + +// ChequebookCaller is an auto generated read-only Go binding around an Ethereum contract. +type ChequebookCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ChequebookTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ChequebookTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ChequebookSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ChequebookSession struct { + Contract *Chequebook // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ChequebookCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ChequebookCallerSession struct { + Contract *ChequebookCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ChequebookTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ChequebookTransactorSession struct { + Contract *ChequebookTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ChequebookRaw is an auto generated low-level Go binding around an Ethereum contract. +type ChequebookRaw struct { + Contract *Chequebook // Generic contract binding to access the raw methods on +} + +// ChequebookCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ChequebookCallerRaw struct { + Contract *ChequebookCaller // Generic read-only contract binding to access the raw methods on +} + +// ChequebookTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ChequebookTransactorRaw struct { + Contract *ChequebookTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewChequebook creates a new instance of Chequebook, bound to a specific deployed contract. +func NewChequebook(address common.Address, backend bind.ContractBackend) (*Chequebook, error) { + contract, err := bindChequebook(address, backend.(bind.ContractCaller), backend.(bind.ContractTransactor)) + if err != nil { + return nil, err + } + return &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}}, nil +} + +// NewChequebookCaller creates a new read-only instance of Chequebook, bound to a specific deployed contract. +func NewChequebookCaller(address common.Address, caller bind.ContractCaller) (*ChequebookCaller, error) { + contract, err := bindChequebook(address, caller, nil) + if err != nil { + return nil, err + } + return &ChequebookCaller{contract: contract}, nil +} + +// NewChequebookTransactor creates a new write-only instance of Chequebook, bound to a specific deployed contract. +func NewChequebookTransactor(address common.Address, transactor bind.ContractTransactor) (*ChequebookTransactor, error) { + contract, err := bindChequebook(address, nil, transactor) + if err != nil { + return nil, err + } + return &ChequebookTransactor{contract: contract}, nil +} + +// bindChequebook binds a generic wrapper to an already deployed contract. +func bindChequebook(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ChequebookABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Chequebook *ChequebookRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Chequebook.Contract.ChequebookCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Chequebook *ChequebookRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Chequebook.Contract.ChequebookTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Chequebook *ChequebookRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Chequebook.Contract.ChequebookTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Chequebook *ChequebookCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Chequebook.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Chequebook *ChequebookTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Chequebook.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Chequebook *ChequebookTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Chequebook.Contract.contract.Transact(opts, method, params...) +} + +// Sent is a free data retrieval call binding the contract method 0x7bf786f8. +// +// Solidity: function sent( address) constant returns(uint256) +func (_Chequebook *ChequebookCaller) Sent(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) { + var ( + ret0 = new(*big.Int) + ) + out := ret0 + err := _Chequebook.contract.Call(opts, out, "sent", arg0) + return *ret0, err +} + +// Sent is a free data retrieval call binding the contract method 0x7bf786f8. +// +// Solidity: function sent( address) constant returns(uint256) +func (_Chequebook *ChequebookSession) Sent(arg0 common.Address) (*big.Int, error) { + return _Chequebook.Contract.Sent(&_Chequebook.CallOpts, arg0) +} + +// Sent is a free data retrieval call binding the contract method 0x7bf786f8. +// +// Solidity: function sent( address) constant returns(uint256) +func (_Chequebook *ChequebookCallerSession) Sent(arg0 common.Address) (*big.Int, error) { + return _Chequebook.Contract.Sent(&_Chequebook.CallOpts, arg0) +} + +// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. +// +// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() +func (_Chequebook *ChequebookTransactor) Cash(opts *bind.TransactOpts, beneficiary common.Address, amount *big.Int, sig_v *big.Int, sig_r [32]byte, sig_s [32]byte) (*types.Transaction, error) { + return _Chequebook.contract.Transact(opts, "cash", beneficiary, amount, sig_v, sig_r, sig_s) +} + +// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. +// +// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() +func (_Chequebook *ChequebookSession) Cash(beneficiary common.Address, amount *big.Int, sig_v *big.Int, sig_r [32]byte, sig_s [32]byte) (*types.Transaction, error) { + return _Chequebook.Contract.Cash(&_Chequebook.TransactOpts, beneficiary, amount, sig_v, sig_r, sig_s) +} + +// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. +// +// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() +func (_Chequebook *ChequebookTransactorSession) Cash(beneficiary common.Address, amount *big.Int, sig_v *big.Int, sig_r [32]byte, sig_s [32]byte) (*types.Transaction, error) { + return _Chequebook.Contract.Cash(&_Chequebook.TransactOpts, beneficiary, amount, sig_v, sig_r, sig_s) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Chequebook *ChequebookTransactor) Kill(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Chequebook.contract.Transact(opts, "kill") +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Chequebook *ChequebookSession) Kill() (*types.Transaction, error) { + return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Chequebook *ChequebookTransactorSession) Kill() (*types.Transaction, error) { + return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts) +} + +// MortalABI is the input ABI used to generate the binding from. +const MortalABI = `[{"constant":false,"inputs":[],"name":"kill","outputs":[],"type":"function"}]` + +// MortalBin is the compiled bytecode used for deploying new contracts. +const MortalBin = `0x606060405260008054600160a060020a03191633179055605c8060226000396000f3606060405260e060020a600035046341c0e1b58114601a575b005b60186000543373ffffffffffffffffffffffffffffffffffffffff90811691161415605a5760005473ffffffffffffffffffffffffffffffffffffffff16ff5b56` + +// DeployMortal deploys a new Ethereum contract, binding an instance of Mortal to it. +func DeployMortal(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Mortal, error) { + parsed, err := abi.JSON(strings.NewReader(MortalABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(MortalBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Mortal{MortalCaller: MortalCaller{contract: contract}, MortalTransactor: MortalTransactor{contract: contract}}, nil +} + +// Mortal is an auto generated Go binding around an Ethereum contract. +type Mortal struct { + MortalCaller // Read-only binding to the contract + MortalTransactor // Write-only binding to the contract +} + +// MortalCaller is an auto generated read-only Go binding around an Ethereum contract. +type MortalCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MortalTransactor is an auto generated write-only Go binding around an Ethereum contract. +type MortalTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MortalSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type MortalSession struct { + Contract *Mortal // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MortalCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type MortalCallerSession struct { + Contract *MortalCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// MortalTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type MortalTransactorSession struct { + Contract *MortalTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MortalRaw is an auto generated low-level Go binding around an Ethereum contract. +type MortalRaw struct { + Contract *Mortal // Generic contract binding to access the raw methods on +} + +// MortalCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type MortalCallerRaw struct { + Contract *MortalCaller // Generic read-only contract binding to access the raw methods on +} + +// MortalTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type MortalTransactorRaw struct { + Contract *MortalTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewMortal creates a new instance of Mortal, bound to a specific deployed contract. +func NewMortal(address common.Address, backend bind.ContractBackend) (*Mortal, error) { + contract, err := bindMortal(address, backend.(bind.ContractCaller), backend.(bind.ContractTransactor)) + if err != nil { + return nil, err + } + return &Mortal{MortalCaller: MortalCaller{contract: contract}, MortalTransactor: MortalTransactor{contract: contract}}, nil +} + +// NewMortalCaller creates a new read-only instance of Mortal, bound to a specific deployed contract. +func NewMortalCaller(address common.Address, caller bind.ContractCaller) (*MortalCaller, error) { + contract, err := bindMortal(address, caller, nil) + if err != nil { + return nil, err + } + return &MortalCaller{contract: contract}, nil +} + +// NewMortalTransactor creates a new write-only instance of Mortal, bound to a specific deployed contract. +func NewMortalTransactor(address common.Address, transactor bind.ContractTransactor) (*MortalTransactor, error) { + contract, err := bindMortal(address, nil, transactor) + if err != nil { + return nil, err + } + return &MortalTransactor{contract: contract}, nil +} + +// bindMortal binds a generic wrapper to an already deployed contract. +func bindMortal(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(MortalABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Mortal *MortalRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Mortal.Contract.MortalCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Mortal *MortalRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Mortal.Contract.MortalTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Mortal *MortalRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Mortal.Contract.MortalTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Mortal *MortalCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Mortal.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Mortal *MortalTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Mortal.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Mortal *MortalTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Mortal.Contract.contract.Transact(opts, method, params...) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Mortal *MortalTransactor) Kill(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Mortal.contract.Transact(opts, "kill") +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Mortal *MortalSession) Kill() (*types.Transaction, error) { + return _Mortal.Contract.Kill(&_Mortal.TransactOpts) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Mortal *MortalTransactorSession) Kill() (*types.Transaction, error) { + return _Mortal.Contract.Kill(&_Mortal.TransactOpts) +} + +// OwnedABI is the input ABI used to generate the binding from. +const OwnedABI = `[{"inputs":[],"type":"constructor"}]` + +// OwnedBin is the compiled bytecode used for deploying new contracts. +const OwnedBin = `0x606060405260008054600160a060020a0319163317905560068060226000396000f3606060405200` + +// DeployOwned deploys a new Ethereum contract, binding an instance of Owned to it. +func DeployOwned(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Owned, error) { + parsed, err := abi.JSON(strings.NewReader(OwnedABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(OwnedBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Owned{OwnedCaller: OwnedCaller{contract: contract}, OwnedTransactor: OwnedTransactor{contract: contract}}, nil +} + +// Owned is an auto generated Go binding around an Ethereum contract. +type Owned struct { + OwnedCaller // Read-only binding to the contract + OwnedTransactor // Write-only binding to the contract +} + +// OwnedCaller is an auto generated read-only Go binding around an Ethereum contract. +type OwnedCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OwnedTransactor is an auto generated write-only Go binding around an Ethereum contract. +type OwnedTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OwnedSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type OwnedSession struct { + Contract *Owned // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OwnedCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type OwnedCallerSession struct { + Contract *OwnedCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// OwnedTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type OwnedTransactorSession struct { + Contract *OwnedTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OwnedRaw is an auto generated low-level Go binding around an Ethereum contract. +type OwnedRaw struct { + Contract *Owned // Generic contract binding to access the raw methods on +} + +// OwnedCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type OwnedCallerRaw struct { + Contract *OwnedCaller // Generic read-only contract binding to access the raw methods on +} + +// OwnedTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type OwnedTransactorRaw struct { + Contract *OwnedTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewOwned creates a new instance of Owned, bound to a specific deployed contract. +func NewOwned(address common.Address, backend bind.ContractBackend) (*Owned, error) { + contract, err := bindOwned(address, backend.(bind.ContractCaller), backend.(bind.ContractTransactor)) + if err != nil { + return nil, err + } + return &Owned{OwnedCaller: OwnedCaller{contract: contract}, OwnedTransactor: OwnedTransactor{contract: contract}}, nil +} + +// NewOwnedCaller creates a new read-only instance of Owned, bound to a specific deployed contract. +func NewOwnedCaller(address common.Address, caller bind.ContractCaller) (*OwnedCaller, error) { + contract, err := bindOwned(address, caller, nil) + if err != nil { + return nil, err + } + return &OwnedCaller{contract: contract}, nil +} + +// NewOwnedTransactor creates a new write-only instance of Owned, bound to a specific deployed contract. +func NewOwnedTransactor(address common.Address, transactor bind.ContractTransactor) (*OwnedTransactor, error) { + contract, err := bindOwned(address, nil, transactor) + if err != nil { + return nil, err + } + return &OwnedTransactor{contract: contract}, nil +} + +// bindOwned binds a generic wrapper to an already deployed contract. +func bindOwned(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(OwnedABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Owned *OwnedRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Owned.Contract.OwnedCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Owned *OwnedRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Owned.Contract.OwnedTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Owned *OwnedRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Owned.Contract.OwnedTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Owned *OwnedCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Owned.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Owned *OwnedTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Owned.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Owned *OwnedTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Owned.Contract.contract.Transact(opts, method, params...) +} + diff --git a/common/chequebook/chequebook.sol b/swarm/services/chequebook/contract/chequebook.sol similarity index 85% rename from common/chequebook/chequebook.sol rename to swarm/services/chequebook/contract/chequebook.sol index 836d570472..0468742059 100644 --- a/common/chequebook/chequebook.sol +++ b/swarm/services/chequebook/contract/chequebook.sol @@ -4,19 +4,11 @@ import "mortal"; /// @author Daniel A. Nagy contract chequebook is mortal { // Cumulative paid amount in wei to each beneficiary - mapping (address => uint256) sent; + mapping (address => uint256) public 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) constant returns (uint256) { - return sent[beneficiary]; - } - /// @notice Cash cheque /// /// @param beneficiary beneficiary address diff --git a/swarm/services/chequebook/contract/contract.go b/swarm/services/chequebook/contract/contract.go new file mode 100644 index 0000000000..1e45cbff75 --- /dev/null +++ b/swarm/services/chequebook/contract/contract.go @@ -0,0 +1,5 @@ +// ContractDeployedCode is used to validate the contract +// this constant needs to be updated when the contract code is changed +package contract + +const ContractDeployedCode = `0x606060405260e060020a600035046341c0e1b581146100315780637bf786f814610059578063fbf788d614610071575b005b61002f60005433600160a060020a03908116911614156101fc57600054600160a060020a0316ff5b6100ab60043560016020526000908152604090205481565b61002f600435602435604435606435608435600160a060020a03851660009081526001602052604081205485116100b5575b505050505050565b6060908152602090f35b30600160a060020a039081166c0100000000000000000000000090810260609081529188160260745260888690526048812080825260ff8616608090815260a086905260c0859052909260019260e0926020928290866161da5a03f11561000257505060405151600054600160a060020a03908116911614610136576100a3565b85600160a060020a031660006001600050600089600160a060020a03168152602001908152602001600020600050548703604051809050600060405180830381858888f19350505050156101b057846001600050600088600160a060020a03168152602001908152602001600020600050819055506100a3565b60005460408051600160a060020a03929092168252517f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f9789181900360200190a185600160a060020a0316ff5b56` diff --git a/swarm/services/ens/contract/ens.go b/swarm/services/ens/contract/ens.go new file mode 100644 index 0000000000..52611a58f8 --- /dev/null +++ b/swarm/services/ens/contract/ens.go @@ -0,0 +1,566 @@ +// This file is an automatically generated Go binding. Do not modify as any +// change will likely be lost upon the next re-generation! + +package contract + +import ( + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// ENSABI is the input ABI used to generate the binding from. +const ENSABI = `[{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"Owners","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[],"name":"kill","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"Registry","outputs":[{"name":"","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"host","type":"bytes32"},{"name":"content","type":"bytes32"}],"name":"Set","outputs":[],"type":"function"}]` + +// ENSBin is the compiled bytecode used for deploying new contracts. +const ENSBin = `0x606060405260008054600160a060020a03191633179055610148806100246000396000f3606060405260e060020a60003504633d14b257811461003c57806341c0e1b51461005d578063a0d03d3614610085578063be36e6761461009d575b005b61013c600435600260205260009081526040902054600160a060020a031681565b61003a60005433600160a060020a039081169116141561014657600054600160a060020a0316ff5b61013c60043560016020526000908152604090205481565b61003a600435602435600082815260026020526040812054600160a060020a031614156100e7576040600020805473ffffffffffffffffffffffffffffffffffffffff1916321790555b32600160a060020a03166002600050600084815260200190815260200160002060009054906101000a9004600160a060020a0316600160a060020a0316141561013857600160205260406000208190555b5050565b6060908152602090f35b56` + +// DeployENS deploys a new Ethereum contract, binding an instance of ENS to it. +func DeployENS(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *ENS, error) { + parsed, err := abi.JSON(strings.NewReader(ENSABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ENSBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &ENS{ENSCaller: ENSCaller{contract: contract}, ENSTransactor: ENSTransactor{contract: contract}}, nil +} + +// ENS is an auto generated Go binding around an Ethereum contract. +type ENS struct { + ENSCaller // Read-only binding to the contract + ENSTransactor // Write-only binding to the contract +} + +// ENSCaller is an auto generated read-only Go binding around an Ethereum contract. +type ENSCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ENSTransactor is an auto generated write-only Go binding around an Ethereum contract. +type ENSTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// ENSSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type ENSSession struct { + Contract *ENS // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ENSCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type ENSCallerSession struct { + Contract *ENSCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// ENSTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type ENSTransactorSession struct { + Contract *ENSTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// ENSRaw is an auto generated low-level Go binding around an Ethereum contract. +type ENSRaw struct { + Contract *ENS // Generic contract binding to access the raw methods on +} + +// ENSCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type ENSCallerRaw struct { + Contract *ENSCaller // Generic read-only contract binding to access the raw methods on +} + +// ENSTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type ENSTransactorRaw struct { + Contract *ENSTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewENS creates a new instance of ENS, bound to a specific deployed contract. +func NewENS(address common.Address, backend bind.ContractBackend) (*ENS, error) { + contract, err := bindENS(address, backend.(bind.ContractCaller), backend.(bind.ContractTransactor)) + if err != nil { + return nil, err + } + return &ENS{ENSCaller: ENSCaller{contract: contract}, ENSTransactor: ENSTransactor{contract: contract}}, nil +} + +// NewENSCaller creates a new read-only instance of ENS, bound to a specific deployed contract. +func NewENSCaller(address common.Address, caller bind.ContractCaller) (*ENSCaller, error) { + contract, err := bindENS(address, caller, nil) + if err != nil { + return nil, err + } + return &ENSCaller{contract: contract}, nil +} + +// NewENSTransactor creates a new write-only instance of ENS, bound to a specific deployed contract. +func NewENSTransactor(address common.Address, transactor bind.ContractTransactor) (*ENSTransactor, error) { + contract, err := bindENS(address, nil, transactor) + if err != nil { + return nil, err + } + return &ENSTransactor{contract: contract}, nil +} + +// bindENS binds a generic wrapper to an already deployed contract. +func bindENS(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(ENSABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ENS *ENSRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _ENS.Contract.ENSCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ENS *ENSRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ENS.Contract.ENSTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ENS *ENSRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ENS.Contract.ENSTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_ENS *ENSCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _ENS.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_ENS *ENSTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ENS.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_ENS *ENSTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _ENS.Contract.contract.Transact(opts, method, params...) +} + +// Owners is a free data retrieval call binding the contract method 0x3d14b257. +// +// Solidity: function Owners( bytes32) constant returns(address) +func (_ENS *ENSCaller) Owners(opts *bind.CallOpts, arg0 [32]byte) (common.Address, error) { + var ( + ret0 = new(common.Address) + ) + out := ret0 + err := _ENS.contract.Call(opts, out, "Owners", arg0) + return *ret0, err +} + +// Owners is a free data retrieval call binding the contract method 0x3d14b257. +// +// Solidity: function Owners( bytes32) constant returns(address) +func (_ENS *ENSSession) Owners(arg0 [32]byte) (common.Address, error) { + return _ENS.Contract.Owners(&_ENS.CallOpts, arg0) +} + +// Owners is a free data retrieval call binding the contract method 0x3d14b257. +// +// Solidity: function Owners( bytes32) constant returns(address) +func (_ENS *ENSCallerSession) Owners(arg0 [32]byte) (common.Address, error) { + return _ENS.Contract.Owners(&_ENS.CallOpts, arg0) +} + +// Registry is a free data retrieval call binding the contract method 0xa0d03d36. +// +// Solidity: function Registry( bytes32) constant returns(bytes32) +func (_ENS *ENSCaller) Registry(opts *bind.CallOpts, arg0 [32]byte) ([32]byte, error) { + var ( + ret0 = new([32]byte) + ) + out := ret0 + err := _ENS.contract.Call(opts, out, "Registry", arg0) + return *ret0, err +} + +// Registry is a free data retrieval call binding the contract method 0xa0d03d36. +// +// Solidity: function Registry( bytes32) constant returns(bytes32) +func (_ENS *ENSSession) Registry(arg0 [32]byte) ([32]byte, error) { + return _ENS.Contract.Registry(&_ENS.CallOpts, arg0) +} + +// Registry is a free data retrieval call binding the contract method 0xa0d03d36. +// +// Solidity: function Registry( bytes32) constant returns(bytes32) +func (_ENS *ENSCallerSession) Registry(arg0 [32]byte) ([32]byte, error) { + return _ENS.Contract.Registry(&_ENS.CallOpts, arg0) +} + +// Set is a paid mutator transaction binding the contract method 0xbe36e676. +// +// Solidity: function Set(host bytes32, content bytes32) returns() +func (_ENS *ENSTransactor) Set(opts *bind.TransactOpts, host [32]byte, content [32]byte) (*types.Transaction, error) { + return _ENS.contract.Transact(opts, "Set", host, content) +} + +// Set is a paid mutator transaction binding the contract method 0xbe36e676. +// +// Solidity: function Set(host bytes32, content bytes32) returns() +func (_ENS *ENSSession) Set(host [32]byte, content [32]byte) (*types.Transaction, error) { + return _ENS.Contract.Set(&_ENS.TransactOpts, host, content) +} + +// Set is a paid mutator transaction binding the contract method 0xbe36e676. +// +// Solidity: function Set(host bytes32, content bytes32) returns() +func (_ENS *ENSTransactorSession) Set(host [32]byte, content [32]byte) (*types.Transaction, error) { + return _ENS.Contract.Set(&_ENS.TransactOpts, host, content) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_ENS *ENSTransactor) Kill(opts *bind.TransactOpts) (*types.Transaction, error) { + return _ENS.contract.Transact(opts, "kill") +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_ENS *ENSSession) Kill() (*types.Transaction, error) { + return _ENS.Contract.Kill(&_ENS.TransactOpts) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_ENS *ENSTransactorSession) Kill() (*types.Transaction, error) { + return _ENS.Contract.Kill(&_ENS.TransactOpts) +} + +// MortalABI is the input ABI used to generate the binding from. +const MortalABI = `[{"constant":false,"inputs":[],"name":"kill","outputs":[],"type":"function"}]` + +// MortalBin is the compiled bytecode used for deploying new contracts. +const MortalBin = `0x606060405260008054600160a060020a03191633179055605c8060226000396000f3606060405260e060020a600035046341c0e1b58114601a575b005b60186000543373ffffffffffffffffffffffffffffffffffffffff90811691161415605a5760005473ffffffffffffffffffffffffffffffffffffffff16ff5b56` + +// DeployMortal deploys a new Ethereum contract, binding an instance of Mortal to it. +func DeployMortal(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Mortal, error) { + parsed, err := abi.JSON(strings.NewReader(MortalABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(MortalBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Mortal{MortalCaller: MortalCaller{contract: contract}, MortalTransactor: MortalTransactor{contract: contract}}, nil +} + +// Mortal is an auto generated Go binding around an Ethereum contract. +type Mortal struct { + MortalCaller // Read-only binding to the contract + MortalTransactor // Write-only binding to the contract +} + +// MortalCaller is an auto generated read-only Go binding around an Ethereum contract. +type MortalCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MortalTransactor is an auto generated write-only Go binding around an Ethereum contract. +type MortalTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MortalSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type MortalSession struct { + Contract *Mortal // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MortalCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type MortalCallerSession struct { + Contract *MortalCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// MortalTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type MortalTransactorSession struct { + Contract *MortalTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MortalRaw is an auto generated low-level Go binding around an Ethereum contract. +type MortalRaw struct { + Contract *Mortal // Generic contract binding to access the raw methods on +} + +// MortalCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type MortalCallerRaw struct { + Contract *MortalCaller // Generic read-only contract binding to access the raw methods on +} + +// MortalTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type MortalTransactorRaw struct { + Contract *MortalTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewMortal creates a new instance of Mortal, bound to a specific deployed contract. +func NewMortal(address common.Address, backend bind.ContractBackend) (*Mortal, error) { + contract, err := bindMortal(address, backend.(bind.ContractCaller), backend.(bind.ContractTransactor)) + if err != nil { + return nil, err + } + return &Mortal{MortalCaller: MortalCaller{contract: contract}, MortalTransactor: MortalTransactor{contract: contract}}, nil +} + +// NewMortalCaller creates a new read-only instance of Mortal, bound to a specific deployed contract. +func NewMortalCaller(address common.Address, caller bind.ContractCaller) (*MortalCaller, error) { + contract, err := bindMortal(address, caller, nil) + if err != nil { + return nil, err + } + return &MortalCaller{contract: contract}, nil +} + +// NewMortalTransactor creates a new write-only instance of Mortal, bound to a specific deployed contract. +func NewMortalTransactor(address common.Address, transactor bind.ContractTransactor) (*MortalTransactor, error) { + contract, err := bindMortal(address, nil, transactor) + if err != nil { + return nil, err + } + return &MortalTransactor{contract: contract}, nil +} + +// bindMortal binds a generic wrapper to an already deployed contract. +func bindMortal(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(MortalABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Mortal *MortalRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Mortal.Contract.MortalCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Mortal *MortalRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Mortal.Contract.MortalTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Mortal *MortalRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Mortal.Contract.MortalTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Mortal *MortalCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Mortal.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Mortal *MortalTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Mortal.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Mortal *MortalTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Mortal.Contract.contract.Transact(opts, method, params...) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Mortal *MortalTransactor) Kill(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Mortal.contract.Transact(opts, "kill") +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Mortal *MortalSession) Kill() (*types.Transaction, error) { + return _Mortal.Contract.Kill(&_Mortal.TransactOpts) +} + +// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. +// +// Solidity: function kill() returns() +func (_Mortal *MortalTransactorSession) Kill() (*types.Transaction, error) { + return _Mortal.Contract.Kill(&_Mortal.TransactOpts) +} + +// OwnedABI is the input ABI used to generate the binding from. +const OwnedABI = `[{"inputs":[],"type":"constructor"}]` + +// OwnedBin is the compiled bytecode used for deploying new contracts. +const OwnedBin = `0x606060405260008054600160a060020a0319163317905560068060226000396000f3606060405200` + +// DeployOwned deploys a new Ethereum contract, binding an instance of Owned to it. +func DeployOwned(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Owned, error) { + parsed, err := abi.JSON(strings.NewReader(OwnedABI)) + if err != nil { + return common.Address{}, nil, nil, err + } + address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(OwnedBin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &Owned{OwnedCaller: OwnedCaller{contract: contract}, OwnedTransactor: OwnedTransactor{contract: contract}}, nil +} + +// Owned is an auto generated Go binding around an Ethereum contract. +type Owned struct { + OwnedCaller // Read-only binding to the contract + OwnedTransactor // Write-only binding to the contract +} + +// OwnedCaller is an auto generated read-only Go binding around an Ethereum contract. +type OwnedCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OwnedTransactor is an auto generated write-only Go binding around an Ethereum contract. +type OwnedTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// OwnedSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type OwnedSession struct { + Contract *Owned // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OwnedCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type OwnedCallerSession struct { + Contract *OwnedCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// OwnedTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type OwnedTransactorSession struct { + Contract *OwnedTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// OwnedRaw is an auto generated low-level Go binding around an Ethereum contract. +type OwnedRaw struct { + Contract *Owned // Generic contract binding to access the raw methods on +} + +// OwnedCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type OwnedCallerRaw struct { + Contract *OwnedCaller // Generic read-only contract binding to access the raw methods on +} + +// OwnedTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type OwnedTransactorRaw struct { + Contract *OwnedTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewOwned creates a new instance of Owned, bound to a specific deployed contract. +func NewOwned(address common.Address, backend bind.ContractBackend) (*Owned, error) { + contract, err := bindOwned(address, backend.(bind.ContractCaller), backend.(bind.ContractTransactor)) + if err != nil { + return nil, err + } + return &Owned{OwnedCaller: OwnedCaller{contract: contract}, OwnedTransactor: OwnedTransactor{contract: contract}}, nil +} + +// NewOwnedCaller creates a new read-only instance of Owned, bound to a specific deployed contract. +func NewOwnedCaller(address common.Address, caller bind.ContractCaller) (*OwnedCaller, error) { + contract, err := bindOwned(address, caller, nil) + if err != nil { + return nil, err + } + return &OwnedCaller{contract: contract}, nil +} + +// NewOwnedTransactor creates a new write-only instance of Owned, bound to a specific deployed contract. +func NewOwnedTransactor(address common.Address, transactor bind.ContractTransactor) (*OwnedTransactor, error) { + contract, err := bindOwned(address, nil, transactor) + if err != nil { + return nil, err + } + return &OwnedTransactor{contract: contract}, nil +} + +// bindOwned binds a generic wrapper to an already deployed contract. +func bindOwned(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(OwnedABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Owned *OwnedRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Owned.Contract.OwnedCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Owned *OwnedRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Owned.Contract.OwnedTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Owned *OwnedRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Owned.Contract.OwnedTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Owned *OwnedCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { + return _Owned.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Owned *OwnedTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Owned.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Owned *OwnedTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Owned.Contract.contract.Transact(opts, method, params...) +} diff --git a/swarm/services/ens/contract/ens.sol b/swarm/services/ens/contract/ens.sol new file mode 100644 index 0000000000..9ef19c5e80 --- /dev/null +++ b/swarm/services/ens/contract/ens.sol @@ -0,0 +1,19 @@ +import "mortal"; +/// @title Swarm Distributed Preimage Archive +/// @author Viktor Tron +contract ENS is mortal +{ + + mapping (bytes32 => bytes32) public Registry; + mapping (bytes32 => address) public Owners; + + function Set(bytes32 host, bytes32 content) { + if (Owners[host] == 0x0) { + Owners[host] = tx.origin; + } + if (Owners[host] == tx.origin) { + Registry[host] = content; + } + } + +} \ No newline at end of file diff --git a/swarm/services/ens/ens.go b/swarm/services/ens/ens.go new file mode 100644 index 0000000000..715d06d0ea --- /dev/null +++ b/swarm/services/ens/ens.go @@ -0,0 +1,84 @@ +//go:generate abigen --sol contract/ens.sol --pkg contract --out contract/ens.go +package ens + +import ( + "fmt" + "math/big" + "regexp" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" + "github.com/ethereum/go-ethereum/swarm/services/ens/contract" + "github.com/ethereum/go-ethereum/swarm/storage" +) + +var domainAndVersion = regexp.MustCompile("[@:;,]+") + +// swarm domain name registry and resolver +// the ENS instance can be directly wrapped in rpc.Api +type ENS struct { + *contract.ENSSession +} + +// NewENS creates a proxy instance wrapping the abigen interface to the ENS contract +// using the transaction options passed as first argument, it sets up a session +func NewENS(transactOpts *bind.TransactOpts, contractAddr common.Address, contractBackend bind.ContractBackend) *ENS { + ens, err := contract.NewENS(contractAddr, contractBackend) + if err != nil { + glog.V(logger.Debug).Infof("error setting up name server on %v, skipping: %v", contractAddr.Hex(), err) + } + return &ENS{ + &contract.ENSSession{ + Contract: ens, + TransactOpts: *transactOpts, + }, + } +} + +// Register(name, hash ) +//involves sending a transaction (sent by sender specified as From of Transact) +func (self *ENS) Register(name string, hash common.Hash) (*types.Transaction, error) { + namehash := crypto.Sha3Hash([]byte(name)) + owner, err := self.Owners(namehash) + if err != nil { + return nil, fmt.Errorf("error registering '%s': %v", name, err) + } + if (owner != common.Address{} && owner != self.TransactOpts.From) { + return nil, fmt.Errorf("error registering '%s': already set as %", name) + } + glog.V(logger.Debug).Infof("[ENS]: host '%s' (hash: '%v') to be registered as '%v'", name, namehash.Hex(), hash.Hex()) + return self.Set(namehash, hash) +} + +func (self *ENS) WhoseIs(name string) (common.Address, error) { + namehash := crypto.Sha3Hash([]byte(name)) + return self.Owners(namehash) +} + +// resolve is a non-tranasctional call, returns hash as storage.Key +func (self *ENS) Resolve(hostPort string) (storage.Key, error) { + host := hostPort + var version *big.Int + parts := domainAndVersion.Split(host, 3) + if len(parts) > 1 && parts[1] != "" { + host = parts[0] + version = common.Big(parts[1]) + } + hash := crypto.Sha3Hash([]byte(host)) + _ = version + // hash, err = self.registrar.Resolver(version).HashToHash(hostHash) + hash, err := self.Registry(hash) + if err != nil { + return nil, fmt.Errorf("error resolving '%v': %v", hash.Hex(), err) + } + if (hash == common.Hash{}) { + return nil, fmt.Errorf("unable to resolve '%v': not found", hash) + } + contentHash := storage.Key(hash.Bytes()) + glog.V(logger.Debug).Infof("[ENS] resolve host '%v' to contentHash: '%v'", hash, contentHash) + return contentHash, nil +} diff --git a/swarm/services/ens/ens_test.go b/swarm/services/ens/ens_test.go new file mode 100644 index 0000000000..0b99730b46 --- /dev/null +++ b/swarm/services/ens/ens_test.go @@ -0,0 +1,56 @@ +package ens + +import ( + "crypto/ecdsa" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/swarm/services/ens/contract" +) + +var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + name = "my name on ENS" + hash = crypto.Sha3Hash([]byte("my content")) + addr = crypto.PubkeyToAddress(key.PublicKey) +) + +func deploy(prvKey *ecdsa.PrivateKey, amount *big.Int, backend *backends.SimulatedBackend) (common.Address, error) { + deployTransactor := bind.NewKeyedTransactor(prvKey) + deployTransactor.Value = amount + addr, _, _, err := contract.DeployENS(deployTransactor, backend) + if err != nil { + return common.Address{}, err + } + backend.Commit() + return addr, nil +} + +func TestENS(t *testing.T) { + contractBackend := backends.NewSimulatedBackend(core.GenesisAccount{addr, big.NewInt(1000000000)}) + transactOpts := bind.NewKeyedTransactor(key) + contractAddr, err := deploy(key, big.NewInt(0), contractBackend) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + ens := NewENS(transactOpts, contractAddr, contractBackend) + _, err = ens.Register(name, hash) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + contractBackend.Commit() + vhost, err := ens.Resolve(name) + if err != nil { + t.Fatalf("expected no error, got %v", err) + } + if vhost.Hex() != hash.Hex()[2:] { + t.Fatalf("resolve error, expected %v, got %v", hash.Hex(), vhost) + // t.Fatalf("resolve error, expected %v, got %v", transactOpts.From, hash) + } + +} diff --git a/swarm/services/swap/swap.go b/swarm/services/swap/swap.go index cc44e263c2..366b84c47b 100644 --- a/swarm/services/swap/swap.go +++ b/swarm/services/swap/swap.go @@ -9,12 +9,15 @@ import ( "sync" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/chequebook" - "github.com/ethereum/go-ethereum/common/swap" + "github.com/ethereum/go-ethereum/core/types" "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/services/chequebook" + "github.com/ethereum/go-ethereum/swarm/services/chequebook/contract" + "github.com/ethereum/go-ethereum/swarm/services/swap/swap" ) // SwAP Swarm Accounting Protocol with @@ -31,14 +34,9 @@ var ( autoDepositBuffer = big.NewInt(100000000000000) // buffer that is surplus for fork protection etc (wei) buyAt = big.NewInt(20000000000) // maximum chunk price host is willing to pay (wei) sellAt = big.NewInt(20000000000) // minimum chunk price host requires (wei) - payAt = 100 // threshold that triggers payment request (units) + payAt = 100 // threshold that triggers payment {request} (units) dropAt = 10000 // threshold that triggers disconnect (units) - maxRetries = 5 -) - -var ( - retryInterval = 10 * time.Second ) type SwapParams struct { @@ -52,14 +50,14 @@ type SwapProfile struct { } type PayProfile struct { - PublicKey string // check againsst signature of promise - Contract common.Address // address of chequebook contract - Beneficiary common.Address // recipient address for swarm sales revenue - privateKey *ecdsa.PrivateKey `json:"-"` - publicKey *ecdsa.PublicKey `json:"-"` + PublicKey string // check against signature of promise + Contract common.Address // address of chequebook contract + Beneficiary common.Address // recipient address for swarm sales revenue + privateKey *ecdsa.PrivateKey + publicKey *ecdsa.PublicKey owner common.Address - chbook *chequebook.Chequebook `json:"-"` - backend chequebook.Backend + chbook *chequebook.Chequebook + backend bind.Backend lock sync.RWMutex } @@ -107,12 +105,12 @@ func NewSwap(local *SwapParams, remote *SwapProfile, proto swap.Protocol) (self // insolvent chequebooks suicide so will signal as invalid // TODO: monitoring a chequebooks events var in *chequebook.Inbox - err = chequebook.Validate(remote.Contract, local.backend) + err = bind.Validate(remote.Contract, contract.ContractDeployedCode, local.backend) if err != nil { glog.V(logger.Info).Infof("[BZZ] SWAP invalid contract %v for peer %v: %v)", remote.Contract.Hex()[:8], proto, err) } else { // remote contract valid, create inbox - in, err = chequebook.NewInbox(remote.Contract, local.owner, local.Beneficiary, crypto.ToECDSAPub(common.FromHex(remote.PublicKey)), local.backend) + in, err = chequebook.NewInbox(local.privateKey, remote.Contract, local.Beneficiary, crypto.ToECDSAPub(common.FromHex(remote.PublicKey)), local.backend) if err != nil { glog.V(logger.Warn).Infof("[BZZ] SWAP unable to set up inbox for chequebook contract %v for peer %v: %v)", remote.Contract.Hex()[:8], proto, err) } @@ -120,7 +118,7 @@ func NewSwap(local *SwapParams, remote *SwapProfile, proto swap.Protocol) (self // cheque if local chequebook contract is valid var out *chequebook.Outbox - err = chequebook.Validate(local.Contract, local.backend) + err = bind.Validate(local.Contract, contract.ContractDeployedCode, local.backend) if err != nil { glog.V(logger.Warn).Infof("[BZZ] SWAP unable to set up outbox for peer %v: chequebook contract (owner: %v): %v)", proto, local.owner.Hex(), err) } else { @@ -161,6 +159,10 @@ func (self *SwapParams) Chequebook() *chequebook.Chequebook { return self.chbook } +func (self *SwapParams) Backend() bind.Backend { + return self.backend +} + func (self *SwapParams) PrivateKey() *ecdsa.PrivateKey { return self.privateKey } @@ -173,23 +175,17 @@ func (self *SwapParams) SetKey(prvkey *ecdsa.PrivateKey) { self.publicKey = &prvkey.PublicKey } -const ( - confirmationInterval = 60000000000 - timeout = 30000000000 // 30 sec -) - // setChequebook(path, backend) wraps the // chequebook initialiser and sets up autoDeposit to cover spending. -func (self *SwapParams) SetChequebook(path string, backend chequebook.Backend) (done chan bool, err error) { +func (self *SwapParams) SetChequebook(path string, backend bind.Backend) (done chan bool, err error) { defer self.lock.Unlock() self.lock.Lock() var valid bool done = make(chan bool) self.backend = backend - err = chequebook.Validate(self.Contract, backend) + err = bind.Validate(self.Contract, contract.ContractDeployedCode, backend) if err != nil { - owner := crypto.PubkeyToAddress(*(self.publicKey)) - go self.deployChequebook(owner, path, done) + go self.deployChequebook(path, done) } else { valid = true go func() { @@ -204,40 +200,39 @@ func (self *SwapParams) SetChequebook(path string, backend chequebook.Backend) ( return done, nil } -func (self *SwapParams) deployChequebook(owner common.Address, path string, done chan bool) { - var timer = time.NewTimer(0).C - retries := 0 - var err error +func deploy(deployTransactor *bind.TransactOpts, backend bind.ContractBackend) (*types.Transaction, error) { + _, tx, _, err := contract.DeployChequebook(deployTransactor, backend) + if err != nil { + return nil, err + } + return tx, nil +} + +// prvKey *ecdsa.PrivateKey, amount *big.Int, + +func (self *SwapParams) deployChequebook(path string, done chan bool) { + owner := crypto.PubkeyToAddress(*(self.publicKey)) + glog.V(logger.Info).Infof("[BZZ] SWAP Deploying new chequebook (owner: %v)", owner.Hex()) var valid bool -OUT: - for { - select { - case <-timer: - // this is blocking - glog.V(logger.Info).Infof("[BZZ] SWAP Deploying new chequebook (owner: %v)", owner.Hex()) - var contract common.Address - contract, err = chequebook.Deploy(owner, self.backend, self.AutoDepositBuffer, confirmationInterval, timeout) - if err != nil { - glog.V(logger.Info).Infof("[BZZ] SWAP unable to deploy new chequebook: %v...retrying in %v", err, retryInterval) - if retries >= maxRetries { - glog.V(logger.Info).Infof("[BZZ] SWAP unable to deploy new chequebook: giving up after %v retries", retries) - break OUT - } - retries++ - timer = time.NewTicker(retryInterval).C - } else { - // need to save config at this point - self.lock.Lock() - self.Contract = contract - err = self.newChequebookFromContract(path, self.backend) - if err != nil { - glog.V(logger.Info).Infof("[BZZ] SWAP error initialising cheque book (owner: %v)", owner.Hex()) - } - self.lock.Unlock() - valid = true - break OUT - } - } + + transactOpts := bind.NewKeyedTransactor(self.privateKey) + transactOpts.Value = self.AutoDepositBuffer + contract, err := bind.Deploy(deploy, contract.ContractDeployedCode, transactOpts, bind.DefaultDeployOptions(), self.backend) + if err != nil { + glog.V(logger.Error).Infof("[BZZ] SWAP unable to deploy new chequebook: %v", err) + return + } + + // need to save config at this point + self.lock.Lock() + self.Contract = contract + err = self.newChequebookFromContract(path, self.backend) + self.lock.Unlock() + if err != nil { + glog.V(logger.Warn).Infof("[BZZ] SWAP error initialising cheque book (owner: %v): %v", owner.Hex(), err) + } else { + valid = true + glog.V(logger.Info).Infof("[BZZ] SWAP new chequebook deployed at %v (owner: %v)", contract.Hex(), owner.Hex()) } done <- valid close(done) @@ -245,8 +240,7 @@ OUT: // initialise the chequebook from a persisted json file or create a new one // caller holds the lock -func (self *SwapParams) newChequebookFromContract(path string, backend chequebook.Backend) error { - +func (self *SwapParams) newChequebookFromContract(path string, backend bind.Backend) error { hexkey := common.Bytes2Hex(self.Contract.Bytes()) err := os.MkdirAll(filepath.Join(path, "chequebooks"), os.ModePerm) if err != nil { @@ -254,7 +248,7 @@ func (self *SwapParams) newChequebookFromContract(path string, backend chequeboo } chbookpath := filepath.Join(path, "chequebooks", hexkey+".json") - self.chbook, err = chequebook.LoadChequebook(chbookpath, self.privateKey, backend) + self.chbook, err = chequebook.LoadChequebook(chbookpath, self.privateKey, backend, true) if err != nil { self.chbook, err = chequebook.NewChequebook(chbookpath, self.Contract, self.privateKey, backend) diff --git a/common/swap/swap.go b/swarm/services/swap/swap/swap.go similarity index 96% rename from common/swap/swap.go rename to swarm/services/swap/swap/swap.go index c8cf7a7770..bbf28c6353 100644 --- a/common/swap/swap.go +++ b/swarm/services/swap/swap/swap.go @@ -152,13 +152,13 @@ func (self *Swap) Add(n int) error { self.lock.Lock() self.balance += n if !self.Sells && self.balance > 0 { - glog.V(logger.Detail).Infof("[SWAP] <%v> remote peer cannot have debt (unable to buy)", self.proto, self.balance) + glog.V(logger.Detail).Infof("[SWAP] <%v> remote peer cannot have debt (balance: %v)", self.proto, self.balance) self.proto.Drop() - return fmt.Errorf("[SWAP] <%v> remote peer cannot have debt (unable to buy)", self.proto, self.balance) + return fmt.Errorf("[SWAP] <%v> remote peer cannot have debt (balance: %v)", self.proto, self.balance) } if !self.Buys && self.balance < 0 { - glog.V(logger.Detail).Infof("[SWAP] <%v> we cannot have debt (unable to buy)", self.proto, self.balance) - return fmt.Errorf("[SWAP] <%v> we cannot have debt (unable to buy)", self.proto, self.balance) + glog.V(logger.Detail).Infof("[SWAP] <%v> we cannot have debt (balance: %v)", self.proto, self.balance) + return fmt.Errorf("[SWAP] <%v> we cannot have debt (balance: %v)", self.proto, self.balance) } if self.balance >= int(self.local.DropAt) { glog.V(logger.Detail).Infof("[SWAP] <%v> remote peer has too much debt (balance: %v, disconnect threshold: %v)", self.proto, self.balance, self.local.DropAt) diff --git a/common/swap/swap_test.go b/swarm/services/swap/swap/swap_test.go similarity index 100% rename from common/swap/swap_test.go rename to swarm/services/swap/swap/swap_test.go diff --git a/swarm/services/swear/swear.sol b/swarm/services/swear/swear.sol index 0086abe7b1..dada7446ef 100644 --- a/swarm/services/swear/swear.sol +++ b/swarm/services/swear/swear.sol @@ -17,7 +17,7 @@ contract Swarm } mapping (address => Bee) swarm; - + // block number of transactions presenting chunks mapping (bytes32 => uint) presentedChunks; @@ -111,7 +111,7 @@ contract Swarm b.reporter = msg.sender; Report(signer); } - + /// @notice Present a chunk in order to avoid losing deposit. /// /// @param chunk chunk data diff --git a/swarm/storage/chunker.go b/swarm/storage/chunker.go index 951e389030..c19bb622ec 100644 --- a/swarm/storage/chunker.go +++ b/swarm/storage/chunker.go @@ -97,7 +97,7 @@ func (self *TreeChunker) KeySize() int64 { // String() for pretty printing func (self *Chunk) String() string { - return fmt.Sprintf("Key: %v TreeSize: %v Chunksize: %v\n", self.Key.Log(), self.Size, len(self.SData)) + return fmt.Sprintf("Key: %v TreeSize: %v Chunksize: %v", self.Key.Log(), self.Size, len(self.SData)) } // The treeChunkers own Hash hashes together diff --git a/swarm/storage/database.go b/swarm/storage/database.go index 9d07fae88d..f0b0edd037 100644 --- a/swarm/storage/database.go +++ b/swarm/storage/database.go @@ -6,7 +6,6 @@ package storage import ( "fmt" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/compression/rle" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/iterator" @@ -82,15 +81,3 @@ func (self *LDBDatabase) Close() { // Close the leveldb database self.db.Close() } - -func (self *LDBDatabase) Print() { - iter := self.db.NewIterator(nil, nil) - for iter.Next() { - key := iter.Key() - value := iter.Value() - - fmt.Printf("%x(%d): ", key, len(key)) - node := common.NewValueFromBytes(value) - fmt.Printf("%v\n", node) - } -} diff --git a/swarm/swarm.go b/swarm/swarm.go index d7edbfbcd8..078495d73a 100644 --- a/swarm/swarm.go +++ b/swarm/swarm.go @@ -2,12 +2,14 @@ package swarm import ( "bytes" + "crypto/ecdsa" "fmt" + "math/big" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/chequebook" "github.com/ethereum/go-ethereum/common/httpclient" - "github.com/ethereum/go-ethereum/common/registrar/ethreg" + "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" @@ -15,27 +17,62 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/swarm/api" + httpapi "github.com/ethereum/go-ethereum/swarm/api/http" "github.com/ethereum/go-ethereum/swarm/network" + "github.com/ethereum/go-ethereum/swarm/services/chequebook" + "github.com/ethereum/go-ethereum/swarm/services/ens" "github.com/ethereum/go-ethereum/swarm/storage" ) +const ( + Namespace = "bzz" + Version = "0.1" // versioning reflect POC and release versions +) + +var ENSContractAddr = common.HexToAddress("0x504cdf3992d8f81a4182bd7b24e270d3a28711e3") + // the swarm stack type Swarm struct { - config *api.Config // swarm configuration - api *api.Api // high level api layer (fs/manifest) - dbAccess *network.DbAccess // access to local chunk db iterator and storage counter - storage storage.ChunkStore // internal access to storage, common interface to cloud storage backends - dpa *storage.DPA // distributed preimage archive, the local API to the storage with document level storage/retrieval support - depo network.StorageHandler // remote request handler, interface between bzz protocol and the storage - cloud storage.CloudStore // procurement, cloud storage backend (can multi-cloud) - hive *network.Hive // the logistic manager - client *httpclient.HTTPClient // bzz capable light http client + ethereum *eth.Ethereum + config *api.Config // swarm configuration + api *api.Api // high level api layer (fs/manifest) + dns api.Resolver // DNS registrar + dbAccess *network.DbAccess // access to local chunk db iterator and storage counter + storage storage.ChunkStore // internal access to storage, common interface to cloud storage backends + dpa *storage.DPA // distributed preimage archive, the local API to the storage with document level storage/retrieval support + depo network.StorageHandler // remote request handler, interface between bzz protocol and the storage + cloud storage.CloudStore // procurement, cloud storage backend (can multi-cloud) + hive *network.Hive // the logistic manager + client *httpclient.HTTPClient // bzz capable light http client + contractBackend bind.ContractBackend // abigen contract Backend + backend bind.Backend // simple blockchain Backend + swapEnabled bool +} + +type SwarmAPI struct { + Api *api.Api + Backend bind.Backend + PrvKey *ecdsa.PrivateKey +} + +func (self *Swarm) API() *SwarmAPI { + return &SwarmAPI{ + Api: self.api, + Backend: self.config.Swap.Backend(), + PrvKey: self.config.Swap.PrivateKey(), + } +} + +type Backend interface { + GetTxReceipt(txhash common.Hash) *types.Receipt + BalanceAt(address common.Address) *big.Int } // creates a new swarm service instance // implements node.Service -func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool) (self *Swarm, err error) { +func NewSwarm(ctx *node.ServiceContext, config *api.Config, swapEnabled, syncEnabled bool) (self *Swarm, err error) { if bytes.Equal(common.FromHex(config.PublicKey), storage.ZeroKey) { return nil, fmt.Errorf("empty public key") @@ -45,21 +82,24 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool) } var ethereum *eth.Ethereum - if err := stack.Service(ðereum); err != nil { + if err := ctx.Service(ðereum); err != nil { return nil, fmt.Errorf("unable to find Ethereum service: %v", err) } self = &Swarm{ - config: config, - client: ethereum.HTTPClient(), + config: config, + ethereum: ethereum, + swapEnabled: swapEnabled, + client: ethereum.HTTPClient(), } glog.V(logger.Debug).Infof("[BZZ] Setting up Swarm service components") - // setup local store hash := storage.MakeHashFunc(config.ChunkerParams.Hash) lstore, err := storage.NewLocalStore(hash, config.StoreParams) if err != nil { return } + + // setup local store glog.V(logger.Debug).Infof("[BZZ] Set up local storage") self.dbAccess = network.NewDbAccess(lstore) @@ -69,6 +109,8 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool) self.hive = network.NewHive( common.HexToHash(self.config.BzzKey), // key to hive (kademlia base address) config.HiveParams, // configuration parameters + swapEnabled, // SWAP enabled + syncEnabled, // syncronisation enabled ) glog.V(logger.Debug).Infof("[BZZ] Set up swarm network with Kademlia hive") @@ -78,9 +120,9 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool) // setup cloud storage internal access layer self.storage = storage.NewNetStore(hash, lstore, cloud, config.StoreParams) - glog.V(logger.Debug).Infof("[BZZ] -> Level 0: swarm net store shared access layer to Swarm Chunk Store") + glog.V(logger.Debug).Infof("[BZZ] -> swarm net store shared access layer to Swarm Chunk Store") - // set up Depo (storage handler = remote cloud storage access layer) + // set up Depo (storage handler = cloud storage access layer for incoming remote requests) self.depo = network.NewDepo(hash, lstore, self.storage) glog.V(logger.Debug).Infof("[BZZ] -> REmote Access to CHunks") @@ -89,23 +131,22 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool) glog.V(logger.Debug).Infof("[BZZ] -> Local Access to Swarm") // Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage self.dpa = storage.NewDPA(dpaChunkStore, self.config.ChunkerParams) - glog.V(logger.Debug).Infof("[BZZ] -> Level 1: Document/File API") + glog.V(logger.Debug).Infof("[BZZ] -> Content Store API") + + // set up blockchain and contract interface bindings + glog.V(logger.Debug).Infof("[BZZ] -> native backend for abigen contract bindings") + self.backend = eth.NewContractBackend(ethereum) // set up high level api - backend := api.NewEthApi(ethereum) - backend.UpdateState() - self.api = api.NewApi(self.dpa, ethreg.New(backend), self.config) - // Manifests for Smart Hosting - glog.V(logger.Debug).Infof("[BZZ] -> Level 2: Collection/Directory API") + transactOpts := bind.NewKeyedTransactor(self.config.Swap.PrivateKey()) + // backend := ethereum.ContractBackend() + self.dns = ens.NewENS(transactOpts, ENSContractAddr, self.backend) + glog.V(logger.Debug).Infof("[BZZ] -> Swarm Domain Name Registrar") + + self.api = api.NewApi(self.dpa, self.dns) + // Manifests for Smart Hosting + glog.V(logger.Debug).Infof("[BZZ] -> Web3 virtual server API") - // set chequebook - if swapEnabled { - err = self.SetChequebook(backend) - if err != nil { - return nil, fmt.Errorf("Unable to set chequebook for SWAP: %v", err) - } - glog.V(logger.Debug).Infof("[BZZ] -> cheque book for SWAP") - } return self, nil } @@ -129,6 +170,16 @@ func (self *Swarm) Start(net *p2p.Server) error { net.AddPeer(node) return nil } + // set chequebook + if self.swapEnabled { + err := self.SetChequebook() + if err != nil { + return fmt.Errorf("Unable to set chequebook for SWAP: %v", err) + } + glog.V(logger.Debug).Infof("[BZZ] -> cheque book for SWAP: %v", self.config.Swap.Chequebook()) + } else { + glog.V(logger.Debug).Infof("[BZZ] SWAP disabled: no cheque book set") + } glog.V(logger.Warn).Infof("[BZZ] Starting Swarm service") self.hive.Start( @@ -143,7 +194,7 @@ func (self *Swarm) Start(net *p2p.Server) error { // start swarm http proxy server if self.config.Port != "" { - go api.StartHttpServer(self.api, self.config.Port) + go httpapi.StartHttpServer(self.api, self.config.Port) } glog.V(logger.Debug).Infof("[BZZ] Swarm http proxy started on port: %v", self.config.Port) @@ -154,7 +205,7 @@ func (self *Swarm) Start(net *p2p.Server) error { "bzz": self.config.Port, } for scheme, port := range schemes { - self.client.RegisterScheme(scheme, &api.RoundTripper{Port: port}) + self.client.RegisterScheme(scheme, &httpapi.RoundTripper{Port: port}) } glog.V(logger.Debug).Infof("[BZZ] Swarm protocol handlers registered for url schemes: %v", schemes) @@ -182,20 +233,37 @@ func (self *Swarm) Protocols() []p2p.Protocol { return []p2p.Protocol{proto} } +// implements node.Service +// Apis returns the RPC Api descriptors the Swarm implementation offers +func (self *Swarm) APIs() []rpc.API { + return []rpc.API{ + // public APIs. + rpc.API{Namespace, Version, api.NewStorage(self.api), true}, + rpc.API{"ens", Version, self.dns, true}, + rpc.API{Namespace, Version, &Info{self.config, chequebook.ContractParams}, true}, + // admin APIs + rpc.API{Namespace, Version, api.NewFileSystem(self.api), false}, + rpc.API{Namespace, Version, api.NewControl(self.api, self.hive), false}, + // rpc.API{Namespace, Version, api.NewAdmin(self), false}, + // TODO: external apis exposed + rpc.API{"chequebook", chequebook.Version, chequebook.NewApi(self.config.Swap.Chequebook), true}, + } +} + func (self *Swarm) Api() *api.Api { return self.api } -// Backend interface implemented by eth or JSON-IPC client -func (self *Swarm) SetChequebook(backend chequebook.Backend) (err error) { - done, err := self.config.Swap.SetChequebook(self.config.Path, backend) +// +func (self *Swarm) SetChequebook() (err error) { + done, err := self.config.Swap.SetChequebook(self.config.Path, self.backend) if err != nil { return err } go func() { ok := <-done if ok { - glog.V(logger.Info).Infof("[BZZ] Swarm: new chequebook set (%v): saving config file, resetting all connections in the hive", self.config.Swap.Contract) + glog.V(logger.Info).Infof("[BZZ] Swarm: new chequebook set (%v): saving config file, resetting all connections in the hive", self.config.Swap.Contract.Hex()) self.config.Save() self.hive.DropAll() } @@ -223,9 +291,19 @@ func NewLocalSwarm(datadir, port string) (self *Swarm, err error) { } self = &Swarm{ - api: api.NewApi(dpa, nil, config), + api: api.NewApi(dpa, nil), config: config, } return } + +// serialisable info about swarm +type Info struct { + *api.Config + *chequebook.Params +} + +func (self *Info) Info() *Info { + return self +} diff --git a/swarm/test/connections/00.sh b/swarm/test/connections/00.sh new file mode 100644 index 0000000000..7f4c4445e9 --- /dev/null +++ b/swarm/test/connections/00.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +dir=`dirname $0` +source $dir/../../cmd/swarm/test.sh + +swarm init 4 +echo "expect each node to have 3 peers" +cmd="'net.peerCount'" +sleep 5 +swarm attach 00 --exec "$cmd"|tail -n1|grep -ql 3&& echo "PASS"||echo "FAIL" +swarm attach 01 --exec "$cmd"|tail -n1|grep -ql 3&& echo "PASS"||echo "FAIL" +swarm attach 02 --exec "$cmd"|tail -n1|grep -ql 3&& echo "PASS"||echo "FAIL" +swarm attach 03 --exec "$cmd"|tail -n1|grep -ql 3&& echo "PASS"||echo "FAIL" + +swarm stop all + +echo "after static nodes is deleted, connections are recovered from kaddb in bzz-peers.json" +# echo rm -rf $DATA_ROOT/enodes\* +# echo rm -rf $DATA_ROOT/data/\*/static-nodes.json +rm -rf $DATA_ROOT/enodes* +rm -rf $DATA_ROOT/data/*/static-nodes.json + +swarm cluster 4 +echo "expect each node to have 3 peers" +cmd="'net.peerCount'" +sleep 10 +swarm attach 00 --exec "$cmd" |tail -n1|grep -ql 3&& echo "PASS"||echo "FAIL" +swarm attach 01 --exec "$cmd"|tail -n1|grep -ql 3&& echo "PASS"||echo "FAIL" +swarm attach 02 --exec "$cmd"|tail -n1|grep -ql 3&& echo "PASS"||echo "FAIL" +swarm attach 03 --exec "$cmd"|tail -n1|grep -ql 3&& echo "PASS"||echo "FAIL" + +swarm stop all + diff --git a/swarm/test/swap/00.sh b/swarm/test/swap/00.sh new file mode 100644 index 0000000000..0cb0b05a4d --- /dev/null +++ b/swarm/test/swap/00.sh @@ -0,0 +1,29 @@ +#!/bin/bash +echo "TEST swap/00:" +echo " two nodes that do not sync and do not have any funds" +echo " cannot retrieve content from each other" + +dir=`dirname $0` +source $dir/../../cmd/swarm/test.sh + +FILE_00=/tmp/1K.0 +randomfile 1 > $FILE_00 +ls -l $FILE_00 +mininginterval=50 +key=/tmp/key + +swarm init 2 --bzznosync +sleep $wait +swarm up 00 $FILE_00|tail -n1 > $key +swarm needs 00 $key $FILE_00 +echo -n "node 01 cannot download file 0: " +swarm needs 01 $key $FILE_00 | tail -1| grep -ql "PASS" && echo "FAIL" || echo "PASS" + +FILE_01=/tmp/1K.1 +randomfile 1 > $FILE_01 +swarm up 01 $FILE_01|tail -1 > $key +swarm needs 01 $key $FILE_01 +echo -n "node 00 cannot download file 1: " +swarm needs 00 $key $FILE_01 | tail -1| grep -ql "PASS" && echo "FAIL" || echo "PASS" + +swarm stop all \ No newline at end of file diff --git a/swarm/test/swap/01.sh b/swarm/test/swap/01.sh new file mode 100644 index 0000000000..362f0da73a --- /dev/null +++ b/swarm/test/swap/01.sh @@ -0,0 +1,43 @@ +echo "TEST swap/01:" +echo " two nodes that do not sync but have enough funds" +echo " can retrieve content from each other" + +dir=`dirname $0` +source $dir/../../cmd/swarm/test.sh + +file=/tmp/test.file +mininginterval=120 +key=/tmp/key +logargs="--verbosity=0 --vmodule='swarm/*=6'" +# logargs='--verbosity=6' + + +swarm init 2 --mine --bzznosync $logargs + +echo "Mining some ether..." +sleep $mininginterval + +randomfile 10 > $file +swarm up 00 $file|tail -n1 > $key +swarm needs 01 $key $file +swarm info 01 + +randomfile 200000 > $file +swarm up 00 $file |tail -n1 > $key +swarm needs 01 $key $file + +randomfile 10 > $file +swarm up 00 $file|tail -n1 > $key +swarm needs 01 $key $file | tail -1| grep -ql "PASS" && echo "FAIL" || echo "PASS <3" + +swarm status 00 +swarm status 01 + +randomfile 10 > $file +swarm up 01 $file|tail -n1 > $key +swarm needs 00 $key $file | tail -1| grep -ql "PASS" && echo "FAIL" || echo "PASS <3" + +swarm status 00 +swarm status 01 + +swarm stop all diff --git a/swarm/test/syncing/00.sh b/swarm/test/syncing/00.sh index 281087f701..2eb1cd140c 100644 --- a/swarm/test/syncing/00.sh +++ b/swarm/test/syncing/00.sh @@ -1,83 +1,55 @@ #!/bin/bash +echo "TEST sync/00:" +echo " two nodes that sync (no swap and do not have any funds)" +echo " can be in sync content with each other" -function up { #file port - echo "Upload file '$1' to node $2 on port 85$2" 1>&2 - key=`bash swarm/cmd/bzzup.sh $1 85$2` - echo -n $key -} +dir=`dirname $0` +source $dir/../../cmd/swarm/tesst.sh -function down { #key port - echo "Download hash '$1' from node $2 on port 85$2" - wget -O- http://localhost:85$2/$1 > /dev/null && echo "got it" || echo "not found" -} +mkdir -p /tmp/swarm-test-files +FILE_00=/tmp/swarm-test-files/00 +FILE_01=/tmp/swarm-test-files/01 +FILE_02=/tmp/swarm-test-files/02 +FILE_03=/tmp/swarm-test-files/03 +FILE_04=/tmp/swarm-test-files/04 -function clean { #index - echo "Clean up for $1" - rm -rf ~/tmp/sync/$1/{bzz/*/chunks,bzz/*/requests/,bzz/*/bzz-peers.json,chaindata,nodes} -} +for f in $FILE_00 $FILE_01 $FILE_02 $FILE_03 $FILE_04; do + randomfile 20 > $f +done +# options="--verbosity=0 --vmodule=swarm/network/*=6,common/chequebook/*=6,common/swap/*=6,common/kademlia/*=5" -function gethup { #index account - cp geth geth$1 - echo "start node $1" - echo "./geth$1 --datadir ~/tmp/sync/$1 --bzzaccount $2 --unlock 0 --password <(echo bzz) --networkid 323 --port 303$1 --vmodule netstore=6,depo=6,forwarding=6,hive=5,dpa=6,dpa=6,http=6,syncb=6,syncer=6,protocol=6,swap=6,chequebook=6 --shh=false --nodiscover --maxpeers 20 --dev --vmdebug=false --verbosity 4 2> sync$1.log &" - ./geth$1 --datadir ~/tmp/sync/$1 --bzzaccount "$2" --unlock 0 --password <(echo bzz) --networkid 323 --port 303$1 --vmodule netstore=6,depo=6,forwarding=6,hive=5,dpa=6,dpa=6,http=6,syncb=6,syncer=6,protocol=6,swap=6,chequebook=6 --shh=false --nodiscover --maxpeers 20 --dev --vmdebug=false --verbosity 4 2> sync$1.log -} +key=/tmp/key +swarm init 2 $options +swarm info 00 +swarm info 01 +swarm up 00 $FILE_00|tail -n1 > $key +swarm needs 00 $key $FILE_00 +# sleep $wait +swarm needs 01 $key $FILE_00 +swarm stop 01 -function gethdown { #index - killall -INT geth$1 -} +# exit 1; -wait=5 -gethdown 00 -gethdown 01 +swarm up 00 $FILE_01|tail -n1 > $key +swarm needs 00 $key $FILE_01 +swarm start 01 $options +swarm needs 01 $key $FILE_01 -# ./geth --datadir ~/tmp/sync/00 --password <(echo bzz) account new -BZZKEY00=5c6332e46a095feb9da1ed9803af2fa425f96aa6 -BZZKEY01=7dafa7436cba4b5b94a0452557b20b01d23bdc05 -echo "Fresh start, wipe datadir" -clean 00 -clean 01 +swarm up 00 $FILE_02|tail -n1 > $key +swarm needs 00 $key $FILE_02 +swarm needs 01 $key $FILE_02 -gethup 00 $BZZKEY00 & -sleep 2 -echo "beyond\n" -key=$(up COPYING.LESSER 00) -echo "beyond\n" -down $key 00 +swarm up 01 $FILE_03|tail -n1 > $key +swarm needs 01 $key $FILE_03 +swarm needs 00 $key $FILE_03 -echo "beyond\n" -gethup 01 $BZZKEY01 & -sleep $wait -down $key 01 -gethdown 01 +swarm stop 00 +swarm up 01 $FILE_04|tail -n1 > $key +swarm needs 01 $key $FILE_04 +swarm start 00 #--bzznoswap +swarm needs 00 $key $FILE_04 - -key=$(up AUTHORS 00) -down $key 00 -gethup 01 $BZZKEY01 & -sleep $wait -down $key 01 - -key=$(up COPYING 00) -down $key 00 -down $key 01 - -key=$(up README.md 01) -down $key 01 -sleep $wait -down $key 00 - -exit 0 - -gethdown 00 -key=$(up README.md 01) -down $key 01 -gethup 00 $BZZKEY00 & -sleep $wait -down $key 00 - -gethdown 00 -gethdown 01 +swarm stop all diff --git a/swarm/test/syncing/01.sh b/swarm/test/syncing/01.sh new file mode 100644 index 0000000000..4e152dcd9f --- /dev/null +++ b/swarm/test/syncing/01.sh @@ -0,0 +1,24 @@ +#!/bin/bash +echo "TEST sync/01:" +echo " two nodes that do not have any funds" +echo " can still sync content with each other" + +dir=`dirname $0` +source $dir/../../cmd/swarm/test.sh +key=/tmp/key + +long=/tmp/10M +randomfile 10000 > $long +ls -l $long + + +swarm init 2 +sleep $wait +swarm up 00 $long |tail -n1 > $key +sleep $wait +swarm stop 01 + +swarm start 01 +swarm needs 01 $key $long + +swarm stop all \ No newline at end of file diff --git a/swarm/test/syncing/02.sh b/swarm/test/syncing/02.sh new file mode 100644 index 0000000000..48760d6e97 --- /dev/null +++ b/swarm/test/syncing/02.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +echo "TEST sync/02:" +echo " two nodes that sync (no swap and do not have any funds)" +echo " can sync content with each other even with intermittent network connection" + +dir=`dirname $0` +source $dir/../../cmd/swarm/test.sh + +long=/tmp/10M +key=/tmp/key +randomfile 10000 > $long +ls -l $long + +swarm init 2 +sleep $wait +swarm up 00 $long |tail -n1 > $key & +sleep $wait +swarm attach 01 -exec "'bzz.blockNetworkRead(true)'" +sleep $wait +swarm attach 01 -exec "'bzz.blockNetworkRead(false)'" +sleep $wait +swarm attach 01 -exec "'bzz.blockNetworkRead(true)'" +sleep $wait +swarm stop 01 + +swarm start 01 +swarm needs 01 $key $long + +swarm stop all \ No newline at end of file