SWORM - swarm poc 0.2 homestead RC1

synced up to go-ethereum 1.5.0-unstable develop branch

major features:
* api overhaul due to rpc v2 and node service stack interface changes
* blockchain/ethereum contract interaction rewritten using abi/abigen (chequebook, ens)
* swarm - cluster control CLI - migration and revamp of prehistoric eth-utils repo
* poor man's end to end testing: scripted scenarios in swarm/test using swarm CLI
* http proxy now handles 3 url schemes for 1) ens-enabled [bzz], 2) immutable [bzzi] and 3) raw manifest [bzzr] resolution
* fixes issues with remote address setting, forwarding and syncing
* new control flags to switch swap and sync on and off
* placeholder basic implementation Ethereum Name Service
* improved logging - now debug level is coherent

regression:
* uri based versioning support is dropped temporarily since state tree pruning does not guarantee historical record
* registrar related functionality temporarily restricted - current ENS provides basic free and unrestricted Register/Resolve

accounts/abi:
  * bind: repeated attempt deployment of contracts, validation against known code, transactor creation from private keys
  * accountmanager: getUnlocked snatch private key when unlocked

cmd:
  * unlockAccount  moved to utils/cmd   and exported
  * getPassPhrase  moved to utils/input and exported
  * accountcmds: reflect the change
  * js: GlobalRegistrar is dropped (ens)

flags:
  * chequebook, bzzaccount,  bzzport, bzzconfig, bzznoswap, bzznosync

chequebook:
  * move from common/ to swarm/services
  * abigen-ised
  * specifies its own API (removed chequebook api from swarm/api)

kademlia:
  * move from common to swarm/network/kademlia
  * address abstracted out to separate file + tests

dns/ens/registrar:
  * moved from swarm/api to swarm/services/ens
  * implementation is basic placeholder before ENS is implemented
  * temporary rpc api via ens namespace
  * the old common/registrar is removed (also from eth/backend apis)

swap:
  * the abstract swap module moved from common to swarm/services
  * now embedded in the swarm and chequebook specific setup (this will change)
  * safer chequebook deployment using abigen helpers
  * backend not field of swap params

eth:
  * public accessor for GPO, needed to construct a PublicBlockChainAPI
  * extends ContractBackend in eth/bind.go with BalanceAt, GetTxReceipt and CodeAt API calls

internal/web3ext
  * add js bindings for bzz, chequebook rpc apis

swarm/api:
  * refactored api into smaller modules filesystem/storage/testapi
  * ethereum backend (needed for dns, swap, etc) moved to abi/bind
  * TODO: further refactor  due to #2040
  * swarm/api/http: now supports the 3 uri schemes
  * examples/album updated

swarm/cmd:
  * migrate old eth-utils and modify into a cluster control CLI
  * bzzup now allows non-local gateway, endpoint specified as second argument

swarm/network:
  * forwarder improved log messages, fixup condition on whether syncer is nil
  * hive extended with controls for testing support block read/write, swap/sync enabled/disabled
  * hive keepAlive launches with alarm in case no discover and no kaddb
  * fix IP address formatting issue [::1] -> became ::1 which refused to dial, now use discover.NewNode#String
  * integrate functionality for enabling/disabling sync and swap
  * allow nil sync state - improve syncer interface in protocol
  * fix SData slice out of bounds bug
  * fix disconnects by not replacing a node when bucket is full

swarm/test
  * poor man's testing framework. scripts invoking swarm/cmd/swarm
  * added tests for basic scenarios connections, swap, sync

swarm:
  * rewrite api using rpc v2
  * blockchain comms via abi/abigen + eth.ContractBackend
  * integrate new flags
This commit is contained in:
zelig 2015-12-14 21:34:17 +00:00
parent f732cd391a
commit f7d5ce4985
86 changed files with 4266 additions and 4312 deletions

109
accounts/abi/bind/ext.go Normal file
View file

@ -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
}

View file

@ -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) { func (am *Manager) GetUnlocked(addr common.Address) (prvkey *ecdsa.PrivateKey, err error) {
am.mutex.RLock() am.mu.RLock()
defer am.mutex.RUnlock() defer am.mu.RUnlock()
unlockedKey, found := am.unlocked[addr] unlockedKey, found := am.unlocked[addr]
if !found { if !found {
return nil, ErrLocked return nil, ErrLocked

View file

@ -21,11 +21,8 @@ import (
"io/ioutil" "io/ioutil"
"github.com/codegangsta/cli" "github.com/codegangsta/cli"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
) )
var ( 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. // accountCreate creates a new account into the keystore defined by the CLI flags.
func accountCreate(ctx *cli.Context) { func accountCreate(ctx *cli.Context) {
accman := utils.MakeAccountManager(ctx) 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) account, err := accman.NewAccount(password)
if err != nil { if err != nil {
@ -277,8 +190,8 @@ func accountUpdate(ctx *cli.Context) {
} }
accman := utils.MakeAccountManager(ctx) accman := utils.MakeAccountManager(ctx)
account, oldPassword := unlockAccount(ctx, accman, ctx.Args().First(), 0, nil) account, oldPassword := utils.UnlockAccount(accman, ctx.Args().First(), 0, nil)
newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 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 { if err := accman.Update(account, oldPassword, newPassword); err != nil {
utils.Fatalf("Could not update the account: %v", err) utils.Fatalf("Could not update the account: %v", err)
} }
@ -295,7 +208,7 @@ func importWallet(ctx *cli.Context) {
} }
accman := utils.MakeAccountManager(ctx) 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) acct, err := accman.ImportPreSaleKey(keyJson, passphrase)
if err != nil { if err != nil {
@ -314,7 +227,7 @@ func accountImport(ctx *cli.Context) {
utils.Fatalf("keyfile must be given as argument") utils.Fatalf("keyfile must be given as argument")
} }
accman := utils.MakeAccountManager(ctx) 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) acct, err := accman.ImportECDSA(key, passphrase)
if err != nil { if err != nil {
utils.Fatalf("Could not create the account: %v", err) utils.Fatalf("Could not create the account: %v", err)

View file

@ -30,7 +30,6 @@ import (
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/registrar"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/internal/web3ext" "github.com/ethereum/go-ethereum/internal/web3ext"
re "github.com/ethereum/go-ethereum/jsre" re "github.com/ethereum/go-ethereum/jsre"
@ -217,8 +216,6 @@ func (js *jsre) apiBindings() error {
utils.Fatalf("Error setting namespaces: %v", err) 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 // overrule some of the methods that require password as input and ask for it interactively
p, err := js.re.Get("personal") p, err := js.re.Get("personal")
if err != nil { if err != nil {

View file

@ -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
// }
})
}

View file

@ -29,7 +29,6 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/accounts" "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"
"github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/common/compiler"
"github.com/ethereum/go-ethereum/common/httpclient" "github.com/ethereum/go-ethereum/common/httpclient"
@ -43,18 +42,17 @@ import (
const ( const (
testSolcPath = "" testSolcPath = ""
solcVersion = "0.9.23" solcVersion = "0.9.23"
testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
testKey = "e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674" testBalance = "10000000000000000000"
testAddress = "0x8605cdbbdb6d264aa742e77020dcbc58fcdce182"
testBalance = "10000000000000000000"
// of empty string // of empty string
testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" testHash = "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"
) )
var ( var (
versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`)) versionRE = regexp.MustCompile(strconv.Quote(`"compilerVersion":"` + solcVersion + `"`))
testNodeKey = crypto.ToECDSA(common.Hex2Bytes("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f")) testNodeKey, _ = crypto.HexToECDSA("4b50fa71f5c3eeb8fdc452224b2395af2fcc3d125e06c32c82e048c0559db03f")
testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}` testAccount, _ = crypto.HexToECDSA("e6fab74a43941f82d89cb7faa408e227cdad3153c4720e540e855c19b15e6674")
testGenesis = `{"` + testAddress[2:] + `": {"balance": "` + testBalance + `"}}`
) )
type testjethre struct { type testjethre struct {
@ -63,17 +61,6 @@ type testjethre struct {
client *httpclient.HTTPClient client *httpclient.HTTPClient
} }
func (self *testjethre) UnlockAccount(acc []byte) bool {
var ethereum *eth.Ethereum
self.stack.Service(&ethereum)
err := ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "")
if err != nil {
panic("unable to unlock")
}
return true
}
// Temporary disabled while natspec hasn't been migrated // Temporary disabled while natspec hasn't been migrated
//func (self *testjethre) ConfirmTransaction(tx string) bool { //func (self *testjethre) ConfirmTransaction(tx string) bool {
// var ethereum *eth.Ethereum // var ethereum *eth.Ethereum
@ -95,18 +82,19 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod
t.Fatal(err) t.Fatal(err)
} }
// Create a networkless protocol stack // 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 { if err != nil {
t.Fatalf("failed to create node: %v", err) t.Fatalf("failed to create node: %v", err)
} }
// Initialize and register the Ethereum protocol // Initialize and register the Ethereum protocol
keystore := crypto.NewKeyStorePlain(filepath.Join(tmp, "keystore")) accman := accounts.NewPlaintextManager(filepath.Join(tmp, "keystore"))
accman := accounts.NewManager(keystore)
db, _ := ethdb.NewMemDatabase() 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 := &eth.Config{ ethConf := &eth.Config{
ChainConfig: &core.ChainConfig{HomesteadBlock: new(big.Int)},
TestGenesisState: db, TestGenesisState: db,
AccountManager: accman, AccountManager: accman,
DocRoot: "/", 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) t.Fatalf("failed to register ethereum protocol: %v", err)
} }
// Initialize all the keys for testing // Initialize all the keys for testing
keyb, err := crypto.HexToECDSA(testKey) a, err := accman.ImportECDSA(testAccount, "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
key := crypto.NewKeyFromECDSA(keyb) if err := accman.Unlock(a, ""); err != nil {
if err := keystore.StoreKey(key, ""); err != nil {
t.Fatal(err)
}
if err := accman.Unlock(key.Address, ""); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Start the node and assemble the REPL tester // 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(&ethereum) stack.Service(&ethereum)
assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext") assetPath := filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "ethereum", "go-ethereum", "cmd", "mist", "assets", "ext")
//client := comms.NewInProcClient(codec.JSON) client, err := stack.Attach()
client := utils.NewInProcRPCClient(stack) if err != nil {
t.Fatalf("failed to attach to node: %v", err)
}
tf := &testjethre{client: ethereum.HTTPClient()} tf := &testjethre{client: ethereum.HTTPClient()}
repl := newJSRE(stack, assetPath, "", client, false) repl := newJSRE(stack, assetPath, "", client, false)
tf.jsre = repl tf.jsre = repl
@ -152,9 +138,6 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *nod
func TestNodeInfo(t *testing.T) { func TestNodeInfo(t *testing.T) {
t.Skip("broken after p2p update") t.Skip("broken after p2p update")
tmp, repl, ethereum := testJEthRE(t) tmp, repl, ethereum := testJEthRE(t)
if err := ethereum.Start(); err != nil {
t.Fatalf("error starting ethereum: %v", err)
}
defer ethereum.Stop() defer ethereum.Stop()
defer os.RemoveAll(tmp) defer os.RemoveAll(tmp)
@ -398,7 +381,7 @@ multiply7 = Multiply7.at(contractaddress);
if sol != nil && solcVersion != sol.Version() { if sol != nil && solcVersion != sol.Version() {
modContractInfo := versionRE.ReplaceAll(contractInfo, []byte(`"compilerVersion":"`+sol.Version()+`"`)) modContractInfo := versionRE.ReplaceAll(contractInfo, []byte(`"compilerVersion":"`+sol.Version()+`"`))
fmt.Printf("modified contractinfo:\n%s\n", modContractInfo) 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 { if checkEvalJSON(t, repl, `filename = "/tmp/info.json"`, `"/tmp/info.json"`) != nil {
return return

View file

@ -4,7 +4,7 @@
// go-ethereum is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or // 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, // go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // 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.ExecFlag,
utils.PreLoadJSFlag, utils.PreLoadJSFlag,
utils.WhisperEnabledFlag, utils.WhisperEnabledFlag,
utils.SwarmConfigPathFlag,
utils.SwarmAccountAddrFlag,
utils.ChequebookAddrFlag,
utils.DevModeFlag, utils.DevModeFlag,
utils.TestNetFlag, utils.TestNetFlag,
utils.VMForceJitFlag, utils.VMForceJitFlag,
@ -236,6 +233,13 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
utils.GpobaseStepUpFlag, utils.GpobaseStepUpFlag,
utils.GpobaseCorrectionFactorFlag, utils.GpobaseCorrectionFactorFlag,
utils.ExtraDataFlag, 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...) app.Flags = append(app.Flags, debug.Flags...)
@ -440,16 +444,6 @@ func startNode(ctx *cli.Context, stack *node.Node) {
if err := stack.Service(&ethereum); err != nil { if err := stack.Service(&ethereum); err != nil {
utils.Fatalf("ethereum service not running: %v", err) 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 ctx.GlobalBool(utils.MiningEnabledFlag.Name) {
if err := ethereum.StartMining(ctx.GlobalInt(utils.MinerThreadsFlag.Name), ctx.GlobalString(utils.MiningGPUFlag.Name)); err != nil { if err := ethereum.StartMining(ctx.GlobalInt(utils.MinerThreadsFlag.Name), ctx.GlobalString(utils.MiningGPUFlag.Name)); err != nil {
utils.Fatalf("Failed to start mining: %v", err) 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) { func makedag(ctx *cli.Context) {
args := ctx.Args() args := ctx.Args()
wrongArgs := func() { wrongArgs := func() {

View file

@ -24,6 +24,7 @@ import (
"os/signal" "os/signal"
"regexp" "regexp"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "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) glog.Infoln("Exported blockchain to", fn)
return nil 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
}

View file

@ -50,12 +50,6 @@ import (
"github.com/ethereum/go-ethereum/pow" "github.com/ethereum/go-ethereum/pow"
"github.com/ethereum/go-ethereum/release" "github.com/ethereum/go-ethereum/release"
"github.com/ethereum/go-ethereum/rpc" "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" "github.com/ethereum/go-ethereum/swarm"
bzzapi "github.com/ethereum/go-ethereum/swarm/api" bzzapi "github.com/ethereum/go-ethereum/swarm/api"
"github.com/ethereum/go-ethereum/whisper" "github.com/ethereum/go-ethereum/whisper"
@ -357,22 +351,6 @@ var (
Name: "shh", Name: "shh",
Usage: "Enable Whisper", 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 // ATM the url is left to the user and deployment to
JSpathFlag = cli.StringFlag{ JSpathFlag = cli.StringFlag{
Name: "jspath", Name: "jspath",
@ -416,6 +394,32 @@ var (
Usage: "Suggested gas price base correction factor (%)", Usage: "Suggested gas price base correction factor (%)",
Value: 110, 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 // MustMakeDataDir retrieves the currently requested data directory, terminating
@ -664,53 +668,6 @@ func MakePasswordList(ctx *cli.Context) []string {
return lines 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 // MakeSystemNode sets up a local node, configures the services to launch and
// assembles the P2P protocol stack. // assembles the P2P protocol stack.
func MakeSystemNode(name, version string, relconf release.Config, extra []byte, ctx *cli.Context) *node.Node { 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 { if networks > 1 {
Fatalf("The %v flags are mutually exclusive", netFlags) Fatalf("The %v flags are mutually exclusive", netFlags)
} }
// Configure the node's service container
datadir := MustMakeDataDir(ctx) datadir := MustMakeDataDir(ctx)
netprv := MakeNodeKey(ctx) netprv := MakeNodeKey(ctx)
// Configure the node's service container // 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), ",") accounts := strings.Split(ctx.GlobalString(UnlockedAccountFlag.Name), ",")
for i, account := range accounts { for i, account := range accounts {
if trimmed := strings.TrimSpace(account); trimmed != "" { 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 { if err != nil {
Fatalf("Failed to create the protocol stack: %v", err) Fatalf("Failed to create the protocol stack: %v", err)
} }
// eth.Ethereum: ethereum
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return eth.New(ctx, ethConf) return eth.New(ctx, ethConf)
}); err != nil { }); err != nil {
Fatalf("Failed to register the Ethereum service: %v", err) Fatalf("Failed to register the Ethereum service: %v", err)
} }
// Whisper
if shhEnable { if shhEnable {
if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisper.New(), nil }); err != nil { 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) 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) return release.NewReleaseService(ctx, relconf)
}); err != nil { }); err != nil {
Fatalf("Failed to register the Geth release oracle service: %v", err) Fatalf("Failed to register the Geth release oracle service: %v", err)
}
// bzz. Swarm // bzz. Swarm
var bzzconfig *bzzapi.Config var bzzconfig *bzzapi.Config
hexaddr := ctx.GlobalString(SwarmAccountAddrFlag.Name) hexaddr := ctx.GlobalString(SwarmAccountAddrFlag.Name)
if hexaddr != "" { if hexaddr != "" {
swarmaccount := common.HexToAddress(hexaddr) swarmaccount := common.HexToAddress(hexaddr)
if !accman.HasAccount(swarmaccount) { if !accman.HasAddress(swarmaccount) {
Fatalf("swarm account '%v' does not exist: %v", hexaddr, err) Fatalf("swarm account '%v' does not exist: %v", hexaddr, err)
} }
prvkey, err := accman.GetUnlocked(swarmaccount) prvkey, err := accman.GetUnlocked(swarmaccount)
@ -884,13 +839,19 @@ func MakeSystemNode(name, version string, relconf release.Config, extra []byte,
if err != nil { if err != nil {
Fatalf("unable to configure swarm: %v", err) 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) { 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 { }); err != nil {
Fatalf("Failed to register the Swarm service: %v", err) Fatalf("Failed to register the Swarm service: %v", err)
} }
} }
return stack return stack
} }

View file

@ -96,3 +96,33 @@ func (r *userInputReader) ConfirmPrompt(prompt string) (bool, error) {
} }
return false, err 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
}

View file

@ -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 <daniel@ethdev.com>
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);
}
}
}
`
)

File diff suppressed because one or more lines are too long

View file

@ -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 <http://www.gnu.org/licenses/>.
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: &registryAPIBackend{
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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<X> 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):], ""
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}
}
}

View file

@ -196,9 +196,15 @@ func Sign(hash []byte, prv *ecdsa.PrivateKey) (sig []byte, err error) {
if len(hash) != 32 { if len(hash) != 32 {
return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(hash)) 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) seckey := common.LeftPadBytes(prv.D.Bytes(), prv.Params().BitSize/8)
defer zeroBytes(seckey) // defer zeroBytes(seckey)
sig, err = secp256k1.Sign(hash, seckey) sig, err = secp256k1.Sign(hash, seckey)
return return
} }

View file

@ -89,6 +89,7 @@ func TestSign(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("Sign error: %s", err) t.Errorf("Sign error: %s", err)
} }
recoveredPub, err := Ecrecover(msg, sig) recoveredPub, err := Ecrecover(msg, sig)
if err != nil { if err != nil {
t.Errorf("ECRecover error: %s", err) t.Errorf("ECRecover error: %s", err)

View file

@ -21,8 +21,6 @@ import (
"encoding/hex" "encoding/hex"
"math/big" "math/big"
"testing" "testing"
"github.com/ethereum/go-ethereum/common/registrar"
) )
func TestReadBits(t *testing.T) { func TestReadBits(t *testing.T) {
@ -39,34 +37,3 @@ func TestReadBits(t *testing.T) {
check("0000000000012345000000000000000000000000000000000000FEFCF3F8F0") check("0000000000012345000000000000000000000000000000000000FEFCF3F8F0")
check("18F8F8F1000111000110011100222004330052300000000000000000FEFCF3F8F0") 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)
}

View file

@ -33,7 +33,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/compiler" "github.com/ethereum/go-ethereum/common/compiler"
"github.com/ethereum/go-ethereum/common/httpclient" "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"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
@ -342,16 +341,12 @@ func (s *Ethereum) APIs() []rpc.API {
}, { }, {
Namespace: "debug", Namespace: "debug",
Version: "1.0", Version: "1.0",
Service: NewPrivateDebugAPI(s.chainConfig, s), Service: NewPrivateDebugAPI(s.ChainConfig(), s),
}, { }, {
Namespace: "net", Namespace: "net",
Version: "1.0", Version: "1.0",
Service: s.netRPCService, Service: s.netRPCService,
Public: true, 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) AccountManager() *accounts.Manager { return s.accountManager }
func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } 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) TxPool() *core.TxPool { return s.txPool }
func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux }
func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb }

View file

@ -21,6 +21,8 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "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/core/types"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
@ -37,6 +39,7 @@ type ContractBackend struct {
eapi *PublicEthereumAPI // Wrapper around the Ethereum object to access metadata eapi *PublicEthereumAPI // Wrapper around the Ethereum object to access metadata
bcapi *PublicBlockChainAPI // Wrapper around the blockchain to access chain data bcapi *PublicBlockChainAPI // Wrapper around the blockchain to access chain data
txapi *PublicTransactionPoolAPI // Wrapper around the transaction pool to access transaction data txapi *PublicTransactionPoolAPI // Wrapper around the transaction pool to access transaction data
eth *Ethereum
} }
// NewContractBackend creates a new native contract backend using an existing // NewContractBackend creates a new native contract backend using an existing
@ -46,6 +49,7 @@ func NewContractBackend(eth *Ethereum) *ContractBackend {
eapi: NewPublicEthereumAPI(eth), eapi: NewPublicEthereumAPI(eth),
bcapi: NewPublicBlockChainAPI(eth.chainConfig, eth.blockchain, eth.miner, eth.chainDb, eth.gpo, eth.eventMux, eth.accountManager), bcapi: NewPublicBlockChainAPI(eth.chainConfig, eth.blockchain, eth.miner, eth.chainDb, eth.gpo, eth.eventMux, eth.accountManager),
txapi: NewPublicTransactionPoolAPI(eth), txapi: NewPublicTransactionPoolAPI(eth),
eth: eth,
} }
} }
@ -108,3 +112,20 @@ func (b *ContractBackend) SendTransaction(tx *types.Transaction) error {
_, err := b.txapi.SendRawTransaction(common.ToHex(raw)) _, err := b.txapi.SendRawTransaction(common.ToHex(raw))
return err 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))
}

View file

@ -23,26 +23,4 @@ const (
FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks FullSync SyncMode = iota // Synchronise the entire blockchain history from full blocks
FastSync // Quickly download the headers, full sync only at the chain head FastSync // Quickly download the headers, full sync only at the chain head
LightSync // Download only the headers and terminate afterwards 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,
}, ",")
) )

View file

@ -18,17 +18,165 @@
package web3ext package web3ext
var Modules = map[string]string{ var Modules = map[string]string{
"txpool": TxPool_JS, "txpool": TxPool_JS,
"admin": Admin_JS, "admin": Admin_JS,
"personal": Personal_JS, "personal": Personal_JS,
"eth": Eth_JS, "eth": Eth_JS,
"miner": Miner_JS, "miner": Miner_JS,
"debug": Debug_JS, "debug": Debug_JS,
"net": Net_JS, "net": Net_JS,
"bzz": Bzz_JS,
"ens": ENS_JS,
"chequebook": Chequebook_JS,
} }
const TxPool_JS = ` const Bzz_JS = `
web3._extend({ web3._extend({
property: 'bzz',
methods:
[
new web3._extend.Method({
name: 'blockNetworkRead',
call: 'bzz_blockNetworkRead',
params: 1,
inputFormatter: [null]
}),
new web3._extend.Method({
name: 'syncEnabled',
call: 'bzz_syncEnabled',
params: 1,
inputFormatter: [null]
}),
new web3._extend.Method({
name: 'swapEnabled',
call: 'bzz_swapEnabled',
params: 1,
inputFormatter: [null]
}),
new web3._extend.Method({
name: 'download',
call: 'bzz_download',
params: 2,
inputFormatter: [null, null]
}),
new web3._extend.Method({
name: 'upload',
call: 'bzz_upload',
params: 2,
inputFormatter: [null, null]
}),
new web3._extend.Method({
name: 'retrieve',
call: 'bzz_retrieve',
params: 1,
inputFormatter: [null]
}),
new web3._extend.Method({
name: 'store',
call: 'bzz_store',
params: 2,
inputFormatter: [null]
}),
new web3._extend.Method({
name: 'get',
call: 'bzz_get',
params: 1,
inputFormatter: [null]
}),
new web3._extend.Method({
name: 'put',
call: 'bzz_put',
params: 2,
inputFormatter: [null, null]
}),
new web3._extend.Method({
name: 'modify',
call: 'bzz_modify',
params: 4,
inputFormatter: [null, null, null, null]
})
],
properties:
[
new web3._extend.Property({
name: 'hive',
getter: 'bzz_hive'
}),
new web3._extend.Property({
name: 'info',
getter: 'bzz_info',
}),
]
});
`
const ENS_JS = `
web3._extend({
property: 'ens',
methods:
[ new web3._extend.Method({
name: 'register',
call: 'ens_register',
params: 2,
inputFormatter: [null, null]
}),
new web3._extend.Method({
name: 'resolve',
call: 'ens_resolve',
params: 1,
inputFormatter: [null]
}),
]
})
`
const Chequebook_JS = `
web3._extend({
property: 'chequebook',
methods:
[
new web3._extend.Method({
name: 'deposit',
call: 'chequebook_deposit',
params: 1,
inputFormatter: [null]
}),
new web3._extend.Property({
name: 'balance',
getter: 'chequebook_balance',
outputFormatter: web3._extend.utils.toDecimal
}),
new web3._extend.Method({
name: 'cash',
call: 'chequebook_cash',
params: 1,
inputFormatter: [null]
}),
new web3._extend.Method({
name: 'issue',
call: 'chequebook_issue',
params: 2,
inputFormatter: [null, null]
}),
]
});
`
const Personal_JS = `
web3._extend({
property: 'personal',
methods:
[
new web3._extend.Method({
name: 'importRawKey',
call: 'personal_importRawKey',
params: 2
})
]
});
`
const TxPool_JS = `web3._extend({
property: 'txpool', property: 'txpool',
methods: methods:
[ [
@ -176,20 +324,6 @@ web3._extend({
}); });
` `
const Personal_JS = `
web3._extend({
property: 'personal',
methods:
[
new web3._extend.Method({
name: 'importRawKey',
call: 'personal_importRawKey',
params: 2
})
]
});
`
const Eth_JS = ` const Eth_JS = `
web3._extend({ web3._extend({
property: 'eth', property: 'eth',

View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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:
[
]
});
`

View file

@ -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 <http://www.gnu.org/licenses/>.
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(&eth); 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 ""
}

View file

@ -1,24 +1,16 @@
package api package api
import ( import (
"bufio"
"fmt" "fmt"
"io" "io"
"math/big"
"net/http"
"os"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
"github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/common" "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"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/swarm/storage"
) )
var ( var (
@ -27,68 +19,91 @@ var (
domainAndVersion = regexp.MustCompile("[@:;,]+") domainAndVersion = regexp.MustCompile("[@:;,]+")
) )
type Resolver interface {
Resolve(string) (storage.Key, error)
}
/* /*
Api implements webserver/file system related content storage and retrieval Api implements webserver/file system related content storage and retrieval
on top of the dpa on top of the dpa
it is the public interface of the dpa which is included in the ethereum stack it is the public interface of the dpa which is included in the ethereum stack
*/ */
type Api struct { type Api struct {
dpa *storage.DPA dpa *storage.DPA
registrar registrar.VersionedRegistrar dns Resolver
conf *Config
} }
//the api constructor initialises //the api constructor initialises
func NewApi(dpa *storage.DPA, registrar registrar.VersionedRegistrar, conf *Config) (self *Api) { func NewApi(dpa *storage.DPA, dns Resolver) (self *Api) {
return &Api{dpa, registrar, conf} self = &Api{
} dpa: dpa,
dns: dns,
// 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
} }
return return
} }
// Put provides singleton manifest creation and optional name registration // DPA reader API
// on top of dpa store func (self *Api) Retrieve(key storage.Key) storage.SectionReader {
return self.dpa.Retrieve(key)
}
func (self *Api) Store(data storage.SectionReader, wg *sync.WaitGroup) (key storage.Key, err error) {
return self.dpa.Store(data, wg)
}
type ErrResolve error
// DNS Resolver
func (self *Api) Resolve(hostPort string, nameresolver bool) (contentHash storage.Key, err error) {
if hashMatcher.MatchString(hostPort) || self.dns == nil {
glog.V(logger.Detail).Infof("[BZZ] host is a contentHash: '%v'", hostPort)
return storage.Key(common.Hex2Bytes(hostPort)), nil
}
if !nameresolver {
err = fmt.Errorf("'%s' is not a content hash value.", hostPort)
return
}
contentHash, err = self.dns.Resolve(hostPort)
if err != nil {
err = ErrResolve(err)
glog.V(logger.Warn).Infof("[BZZ] DNS error : %v", err)
}
glog.V(logger.Detail).Infof("[BZZ] host lookup: %v -> %v", err)
return
}
func parse(uri string) (hostPort, path string) {
parts := slashes.Split(uri, 3)
var i int
if len(parts) == 0 {
return
}
// beginning with slash is now optional
for len(parts[i]) == 0 {
i++
}
hostPort = parts[i]
for i < len(parts)-1 {
i++
if len(path) > 0 {
path = path + "/" + parts[i]
} else {
path = parts[i]
}
}
glog.V(logger.Debug).Infof("[BZZ] Swarm: host: '%s', path '%s' requested.", hostPort, path)
return
}
func (self *Api) parseAndResolve(uri string, nameresolver bool) (contentHash storage.Key, hostPort, path string, err error) {
hostPort, path = parse(uri)
//resolving host and port
contentHash, err = self.Resolve(hostPort, nameresolver)
glog.V(logger.Debug).Infof("[BZZ] Resolved '%s' to contentHash: '%s', path: '%s'", uri, contentHash, path)
return
}
// Put provides singleton manifest creation on top of dpa store
func (self *Api) Put(content, contentType string) (string, error) { func (self *Api) Put(content, contentType string) (string, error) {
sr := io.NewSectionReader(strings.NewReader(content), 0, int64(len(content))) sr := io.NewSectionReader(strings.NewReader(content), 0, int64(len(content)))
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
@ -106,8 +121,36 @@ func (self *Api) Put(content, contentType string) (string, error) {
return key.String(), nil return key.String(), nil
} }
func (self *Api) Modify(rootHash, path, contentHash, contentType string) (newRootHash string, err error) { // Get uses iterative manifest retrieval and prefix matching
root := common.Hex2Bytes(rootHash) // to resolve path to content using dpa retrieve
// it returns a section reader, mimeType, status and an error
func (self *Api) Get(uri string, nameresolver bool) (reader storage.SectionReader, mimeType string, status int, err error) {
key, _, path, err := self.parseAndResolve(uri, nameresolver)
trie, err := loadManifest(self.dpa, key)
if err != nil {
glog.V(logger.Warn).Infof("[BZZ] Swarm: loadManifestTrie error: %v", err)
return
}
glog.V(logger.Detail).Infof("[BZZ] Swarm: getEntry(%s)", path)
entry, _ := trie.getEntry(path)
if entry != nil {
key = common.Hex2Bytes(entry.Hash)
status = entry.Status
mimeType = entry.ContentType
glog.V(logger.Detail).Infof("[BZZ] Swarm: content lookup key: '%v' (%v)", key, mimeType)
reader = self.dpa.Retrieve(key)
} else {
err = fmt.Errorf("manifest entry for '%s' not found", path)
glog.V(logger.Warn).Infof("[BZZ] Swarm: %v", err)
}
return
}
func (self *Api) Modify(uri, contentHash, contentType string, nameresolver bool) (newRootHash string, err error) {
root, _, path, err := self.parseAndResolve(uri, nameresolver)
trie, err := loadManifest(self.dpa, root) trie, err := loadManifest(self.dpa, root)
if err != nil { if err != nil {
return return
@ -130,327 +173,3 @@ func (self *Api) Modify(rootHash, path, contentHash, contentType string) (newRoo
} }
return trie.hash.String(), nil 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
}

View file

@ -1,205 +1,86 @@
package api package api
import ( import (
"bytes" // "bytes"
"io/ioutil" "io/ioutil"
"os" "os"
"path"
"runtime"
"testing" "testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
//TODO: add tests for resolver/registrar func testApi(t *testing.T, f func(*Api)) {
// 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) {
datadir, err := ioutil.TempDir("", "bzz-test") datadir, err := ioutil.TempDir("", "bzz-test")
if err != nil { if err != nil {
return nil, err t.Fatalf("unable to create temp dir: %v", err)
} }
os.RemoveAll(datadir) os.RemoveAll(datadir)
defer os.RemoveAll(datadir)
dpa, err := storage.NewLocalDPA(datadir) dpa, err := storage.NewLocalDPA(datadir)
if err != nil { if err != nil {
return return
} }
prvkey, _ := crypto.GenerateKey() api := NewApi(dpa, nil)
dpa.Start()
f(api)
dpa.Stop()
}
config, err := NewConfig(datadir, common.Address{}, prvkey) type testResponse struct {
if err != nil { reader storage.SectionReader
return *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) if resp.Status != exp.Status {
api.dpa.Start() 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) { func TestApiPut(t *testing.T) {
api, err := testApi() testApi(t, func(api *Api) {
if err != nil { content := "hello"
t.Errorf("unexpected error: %v", err) exp := expResponse(content, "text/plain", 0)
return // exp := expResponse([]byte(content), "text/plain", 0)
} bzzhash, err := api.Put(content, exp.MimeType)
defer api.dpa.Stop() if err != nil {
expContent := "hello" t.Fatalf("unexpected error: %v", err)
expMimeType := "text/plain" }
expStatus := 0 resp := testGet(t, api, bzzhash)
expSize := len(expContent) checkResponse(t, resp, exp)
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)
} }

View file

@ -91,6 +91,7 @@ func TestConfigWriteRead(t *testing.T) {
t.Fatalf("default config file cannot be read: %v", err) t.Fatalf("default config file cannot be read: %v", err)
} }
exp := strings.Replace(defaultConfig, "TMPDIR", tmp, -1) exp := strings.Replace(defaultConfig, "TMPDIR", tmp, -1)
exp = strings.Replace(exp, "\\", "\\\\", -1)
if string(data) != exp { if string(data) != exp {
t.Fatalf("default config mismatch:\nexpected:\n'%v'\ngot:\n'%v'", exp, string(data)) t.Fatalf("default config mismatch:\nexpected:\n'%v'\ngot:\n'%v'", exp, string(data))

View file

@ -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 &ethApi{
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(&ethApi{
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 }

257
swarm/api/filesystem.go Normal file
View file

@ -0,0 +1,257 @@
package api
import (
"bufio"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/swarm/storage"
)
const maxParallelFiles = 5
type FileSystem struct {
api *Api
}
func NewFileSystem(api *Api) *FileSystem {
return &FileSystem{api}
}
// Upload replicates a local directory as a manifest file and uploads it
// using dpa store
// TODO: localpath should point to a manifest
func (self *FileSystem) Upload(lpath, index string) (string, error) {
var list []*manifestTrieEntry
localpath, err := filepath.Abs(filepath.Clean(lpath))
if err != nil {
return "", err
}
f, err := os.Open(localpath)
if err != nil {
return "", err
}
stat, err := f.Stat()
if err != nil {
return "", err
}
var start int
if stat.IsDir() {
start = len(localpath)
glog.V(logger.Debug).Infof("[BZZ] uploading '%s'", localpath)
err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error {
if (err == nil) && !info.IsDir() {
//fmt.Printf("lp %s path %s\n", localpath, path)
if len(path) <= start {
return fmt.Errorf("Path is too short")
}
if path[:start] != localpath {
return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath)
}
entry := &manifestTrieEntry{
Path: path,
}
list = append(list, entry)
}
return err
})
if err != nil {
return "", err
}
} else {
dir := filepath.Dir(localpath)
start = len(dir)
if len(localpath) <= start {
return "", fmt.Errorf("Path is too short")
}
if localpath[:start] != dir {
return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir)
}
entry := &manifestTrieEntry{
Path: localpath,
}
list = append(list, entry)
}
cnt := len(list)
errors := make([]error, cnt)
done := make(chan bool, maxParallelFiles)
dcnt := 0
for i, entry := range list {
if i >= dcnt+maxParallelFiles {
<-done
dcnt++
}
go func(i int, entry *manifestTrieEntry, done chan bool) {
f, err := os.Open(entry.Path)
if err == nil {
stat, _ := f.Stat()
sr := io.NewSectionReader(f, 0, stat.Size())
wg := &sync.WaitGroup{}
var hash storage.Key
hash, err = self.api.dpa.Store(sr, wg)
if hash != nil {
list[i].Hash = hash.String()
}
wg.Wait()
if err == nil {
first512 := make([]byte, 512)
fread, _ := sr.ReadAt(first512, 0)
if fread > 0 {
mimeType := http.DetectContentType(first512[:fread])
if filepath.Ext(entry.Path) == ".css" {
mimeType = "text/css"
}
list[i].ContentType = mimeType
}
}
f.Close()
}
errors[i] = err
done <- true
}(i, entry, done)
}
for dcnt < cnt {
<-done
dcnt++
}
trie := &manifestTrie{
dpa: self.api.dpa,
}
for i, entry := range list {
if errors[i] != nil {
return "", errors[i]
}
entry.Path = RegularSlashes(entry.Path[start:])
if entry.Path == index {
ientry := &manifestTrieEntry{
Path: "",
Hash: entry.Hash,
ContentType: entry.ContentType,
}
trie.addEntry(ientry)
}
trie.addEntry(entry)
}
err2 := trie.recalcAndStore()
var hs string
if err2 == nil {
hs = trie.hash.String()
}
return hs, err2
}
// Download replicates the manifest path structure on the local filesystem
// under localpath
func (self *FileSystem) Download(bzzpath, localpath string) error {
lpath, err := filepath.Abs(filepath.Clean(localpath))
if err != nil {
return err
}
err = os.MkdirAll(lpath, os.ModePerm)
if err != nil {
return err
}
//resolving host and port
key, _, path, err := self.api.parseAndResolve(bzzpath, true)
if err != nil {
return err
}
// if len(path) > 0 {
// path += "/"
// }
trie, err := loadManifest(self.api.dpa, key)
if err != nil {
glog.V(logger.Warn).Infof("[BZZ] fs.Download: loadManifestTrie error: %v", err)
return err
}
type downloadListEntry struct {
key storage.Key
path string
}
var list []*downloadListEntry
var mde, mderr error
prevPath := lpath
err = trie.listWithPrefix(path, func(entry *manifestTrieEntry, suffix string) { // TODO: paralellize
glog.V(logger.Detail).Infof("[BZZ] fs.Download: %#v", entry)
key := common.Hex2Bytes(entry.Hash)
path := lpath + "/" + suffix
dir := filepath.Dir(path)
if dir != prevPath {
mde = os.MkdirAll(dir, os.ModePerm)
if mde != nil {
mderr = mde
}
prevPath = dir
}
if (mde == nil) && (path != dir+"/") {
list = append(list, &downloadListEntry{key: key, path: path})
}
})
if err == nil {
err = mderr
}
cnt := len(list)
errors := make([]error, cnt)
done := make(chan bool, maxParallelFiles)
dcnt := 0
for i, entry := range list {
if i >= dcnt+maxParallelFiles {
<-done
dcnt++
}
go func(i int, entry *downloadListEntry, done chan bool) {
f, err := os.Create(entry.path) // TODO: path separators
if err == nil {
reader := self.api.dpa.Retrieve(entry.key)
writer := bufio.NewWriter(f)
_, err = io.CopyN(writer, reader, reader.Size()) // TODO: handle errors
err2 := writer.Flush()
if err == nil {
err = err2
}
err2 = f.Close()
if err == nil {
err = err2
}
}
errors[i] = err
done <- true
}(i, entry, done)
}
for dcnt < cnt {
<-done
dcnt++
}
if err != nil {
return err
}
for i, _ := range list {
if errors[i] != nil {
return errors[i]
}
}
return err
}

View file

@ -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)
})
}

View file

@ -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)
}

View file

@ -1,4 +1,4 @@
package api package http
import ( import (
"io/ioutil" "io/ioutil"
@ -22,7 +22,7 @@ func TestRoundTripper(t *testing.T) {
}) })
go http.ListenAndServe(":8600", serveMux) go http.ListenAndServe(":8600", serveMux)
rt := &RoundTripper{"8600"} rt := &RoundTripper{Port: "8600"}
client := httpclient.New("/") client := httpclient.New("/")
client.RegisterProtocol("bzz", rt) client.RegisterProtocol("bzz", rt)
@ -43,8 +43,8 @@ func TestRoundTripper(t *testing.T) {
t.Errorf("expected no error, got %v", err) t.Errorf("expected no error, got %v", err)
return return
} }
if string(content) != "/test.com/path" { if string(content) != "/HTTP/1.1:/test.com/path" {
t.Errorf("incorrect response from http server: expected '%v', got '%v'", "/test.com/path", string(content)) t.Errorf("incorrect response from http server: expected '%v', got '%v'", "/HTTP/1.1:/test.com/path", string(content))
} }
} }

View file

@ -1,7 +1,7 @@
/* /*
A simple http server interface to Swarm A simple http server interface to Swarm
*/ */
package api package http
import ( import (
"bytes" "bytes"
@ -14,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/swarm/api"
) )
const ( const (
@ -21,8 +22,8 @@ const (
) )
var ( var (
bzzPrefix = regexp.MustCompile("^/+bzz:/+") // accepted protocols: bzz (traditional), bzzi (immutable) and bzzr (raw)
rawUrl = regexp.MustCompile("^/+raw/*") bzzPrefix = regexp.MustCompile("^/+bzz[ir]?:/+")
trailingSlashes = regexp.MustCompile("/+$") trailingSlashes = regexp.MustCompile("/+$")
// forever = func() time.Time { return time.Unix(0, 0) } // forever = func() time.Time { return time.Unix(0, 0) }
forever = time.Now forever = time.Now
@ -41,7 +42,7 @@ type sequentialReader struct {
// https://github.com/atom/electron/blob/master/docs/api/protocol.md // https://github.com/atom/electron/blob/master/docs/api/protocol.md
// starts up http server // starts up http server
func StartHttpServer(api *Api, port string) { func StartHttpServer(api *api.Api, port string) {
serveMux := http.NewServeMux() serveMux := http.NewServeMux()
serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
handler(w, r, api) 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) 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 requestURL := r.URL
// This is wrong // This is wrong
// if requestURL.Host == "" { // if requestURL.Host == "" {
@ -61,24 +62,41 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
// return // 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 uri := requestURL.Path
var raw bool var raw, nameresolver bool
var proto string
// HTTP-based URL protocol handler // HTTP-based URL protocol handler
uri = bzzPrefix.ReplaceAllString(uri, "")
glog.V(logger.Debug).Infof("[BZZ] Swarm: BZZ request URI: '%s'", uri) glog.V(logger.Debug).Infof("[BZZ] Swarm: BZZ request URI: '%s'", uri)
path := rawUrl.ReplaceAllStringFunc(uri, func(string) string { path := bzzPrefix.ReplaceAllStringFunc(uri, func(p string) string {
raw = true proto = p
return "" 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 { switch {
case r.Method == "POST" || r.Method == "PUT": 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, reader: r.Body,
ahead: make(map[int64]chan bool), ahead: make(map[int64]chan bool),
}, 0, r.ContentLength), nil) }, 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) http.Error(w, "No PUT to /raw allowed.", http.StatusBadRequest)
return return
} else { } else {
path = regularSlashes(path) path = api.RegularSlashes(path)
mime := r.Header.Get("Content-Type") mime := r.Header.Get("Content-Type")
// TODO proper root hash separation // TODO proper root hash separation
glog.V(logger.Debug).Infof("[BZZ] Modify '%s' to store %v as '%s'.", path, key.Log(), mime) 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 { if err == nil {
glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey) glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey)
w.Header().Set("Content-Type", "text/plain") 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) http.Error(w, "No DELETE to /raw allowed.", http.StatusBadRequest)
return return
} else { } else {
path = regularSlashes(path) path = api.RegularSlashes(path)
glog.V(logger.Debug).Infof("[BZZ] Delete '%s'.", 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 { if err == nil {
glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey) glog.V(logger.Debug).Infof("[BZZ] Swarm replaced manifest by '%s'", newKey)
w.Header().Set("Content-Type", "text/plain") 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, "") path = trailingSlashes.ReplaceAllString(path, "")
if raw { if raw {
// resolving host // resolving host
key, err := api.Resolve(path) key, err := a.Resolve(path, nameresolver)
if err != nil { if err != nil {
glog.V(logger.Error).Infof("[BZZ] Swarm: %v", err) glog.V(logger.Error).Infof("[BZZ] Swarm: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
@ -146,7 +164,7 @@ func handler(w http.ResponseWriter, r *http.Request, api *Api) {
} }
// retrieving content // retrieving content
reader := api.dpa.Retrieve(key) reader := a.Retrieve(key)
glog.V(logger.Debug).Infof("[BZZ] Swarm: Reading %d bytes.", reader.Size()) glog.V(logger.Debug).Infof("[BZZ] Swarm: Reading %d bytes.", reader.Size())
// setting mime type // 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) glog.V(logger.Debug).Infof("[BZZ] Swarm: Structured GET request '%s' received.", uri)
// call to api.getPath on uri reader, mimeType, status, err := a.Get(path, nameresolver)
reader, mimeType, status, err := api.getPath(path)
if err != nil { if err != nil {
if _, ok := err.(errResolve); ok { if _, ok := err.(api.ErrResolve); ok {
glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err) glog.V(logger.Debug).Infof("[BZZ] Swarm: %v", err)
status = http.StatusBadRequest status = http.StatusBadRequest
} else { } else {

View file

@ -145,7 +145,10 @@ func (self *manifestTrie) deleteEntry(path string) {
b := byte(path[0]) b := byte(path[0])
entry := self.entries[b] entry := self.entries[b]
if (entry != nil) && (entry.Path == path) { if entry == nil {
return
}
if entry.Path == path {
self.entries[b] = nil self.entries[b] = nil
return return
} }
@ -290,7 +293,7 @@ func (self *manifestTrie) findPrefixOf(path string) (entry *manifestTrieEntry, p
// file system manifest always contains regularized paths // file system manifest always contains regularized paths
// no leading or trailing slashes, only single slashes inside // 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++ { for i := 0; i < len(path); i++ {
if (path[i] != '/') || ((i > 0) && (path[i-1] != '/')) { if (path[i] != '/') || ((i > 0) && (path[i-1] != '/')) {
res = res + path[i: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) { func (self *manifestTrie) getEntry(spath string) (entry *manifestTrieEntry, fullpath string) {
path := regularSlashes(spath) path := RegularSlashes(spath)
var pos int var pos int
entry, pos = self.findPrefixOf(path) entry, pos = self.findPrefixOf(path)
return entry, path[:pos] return entry, path[:pos]

View file

@ -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)
}

48
swarm/api/storage.go Normal file
View file

@ -0,0 +1,48 @@
package api
type Response struct {
MimeType string
Status int
Size int64
// Content []byte
Content string
}
// implements a service
type Storage struct {
api *Api
}
func NewStorage(api *Api) *Storage {
return &Storage{api}
}
// Put uploads the content to the swarm with a simple manifest speficying
// its content type
func (self *Storage) Put(content, contentType string) (string, error) {
return self.api.Put(content, contentType)
}
// Get retrieves the content from bzzpath and reads the response in full
// It returns the Response object, which serialises containing the
// response body as the value of the Content field
// NOTE: if error is non-nil, sResponse may still have partial content
// the actual size of which is given in len(resp.Content), while the expected
// size is resp.Size
func (self *Storage) Get(bzzpath string) (*Response, error) {
reader, mimeType, status, err := self.api.Get(bzzpath, true)
if err != nil {
return nil, err
}
expsize := reader.Size()
body := make([]byte, expsize)
size, err := reader.Read(body)
if int64(size) == expsize {
err = nil
}
return &Response{mimeType, status, expsize, string(body[:size])}, err
}
func (self *Storage) Modify(rootHash, path, contentHash, contentType string) (newRootHash string, err error) {
return self.api.Modify(rootHash+"/"+path, contentHash, contentType, true)
}

33
swarm/api/storage_test.go Normal file
View file

@ -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)
})
}

30
swarm/api/testapi.go Normal file
View file

@ -0,0 +1,30 @@
package api
import (
"github.com/ethereum/go-ethereum/swarm/network"
)
type Control struct {
api *Api
hive *network.Hive
}
func NewControl(api *Api, hive *network.Hive) *Control {
return &Control{api, hive}
}
func (self *Control) BlockNetworkRead(on bool) {
self.hive.BlockNetworkRead(on)
}
func (self *Control) SyncEnabled(on bool) {
self.hive.SyncEnabled(on)
}
func (self *Control) SwapEnabled(on bool) {
self.hive.SwapEnabled(on)
}
func (self *Control) Hive() string {
return self.hive.String()
}

View file

@ -1,17 +1,19 @@
#! /bin/bash #! /bin/bash
INDEX='index.html' INDEX='index.html'
port="8500" proxy="http://localhost:8500"
delimiter='{"entries":[{' delimiter='{"entries":[{'
if [[ ! -z "$2" ]]; then if [[ ! -z "$2" ]]; then
port="$2" proxy="$2"
fi fi
if [ -f "$1" ]; then 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"` 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 echo
else else
@ -28,8 +30,11 @@ do
name=`echo "$path" | cut -c3-` name=`echo "$path" | cut -c3-`
[ _`basename "$name"` = "_$INDEX" ] && name=`dirname "$name"` [ _`basename "$name"` = "_$INDEX" ] && name=`dirname "$name"`
echo -n "$delimiter" 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"` mime=`mimetype -b "$path"`
if [ "$mime" = "text/plain" ]; then
echo -n $path|grep -q '.css' && mime="text/css"
fi
if [ "_$name" = '_.' ]; then if [ "_$name" = '_.' ]; then
echo -n "\"hash\":\"$hash\",\"contentType\":\"$mime\"" echo -n "\"hash\":\"$hash\",\"contentType\":\"$mime\""
else else
@ -38,7 +43,7 @@ fi
delimiter='},{' delimiter='},{'
done done
echo -n '}]}') | wget -q -O- --post-data=`cat` http://localhost:$port/raw echo -n '}]}') | wget -q -O- --post-data=`cat` $proxy/bzzr:/
echo echo
popd > /dev/null popd > /dev/null

149
swarm/cmd/swarm/gethup.sh Normal file
View file

@ -0,0 +1,149 @@
#!/bin/bash
# Usage:
# bash /path/to/eth-utils/gethup.sh <datadir> <instance_name> <ip_addr>
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 `<rootdir>/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 <rootdir>/<dd>
# - 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

316
swarm/cmd/swarm/swarm.sh Normal file
View file

@ -0,0 +1,316 @@
# !/bin/bash
# bash cluster <root> <network_id> <number_of_nodes> <runid> <local_IP> [[params]...]
# https://github.com/ethereum/go-ethereum/wiki/Setting-up-monitoring-on-local-cluster
# sets up a local ethereum network cluster of nodes
# - <number_of_nodes> is the number of nodes in cluster
# - <root> is the root directory for the cluster, the nodes are set up
# with datadir `<root>/<network_id>/00`, `<root>/ <network_id>/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, `<local_IP>` is substituted
# - if `<network_id>` is not 0, they will not connect to a default client,
# resulting in a private isolated network
# - the nodes log into `<root>/<network_id>/00.<runid>.log`, `<root>/<network_id>/01.<runid>.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<N;++i)); do
id=`printf "%02d" $i`
enode=$dir/enodes/$id.enode
if [ -f "$enode" ]; then
cat "$enode" >> $enodes
echo "," >> $enodes
fi
done
echo "\"\"]" >> $enodes
for ((i=0;i<N;++i)); do
id=`printf "%02d" $i`
mkdir -p $dir/data/$id
echo "launching node $i/$N ---> 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

28
swarm/cmd/swarm/test.sh Normal file
View file

@ -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
}

View file

@ -0,0 +1,135 @@
function uploadFile(files, nr, uri) {
// when uploading complete - redirect to new address
if (files.length <= nr) {
if (uri != "") {
onUploadingComplete(uri);
}
return;
}
var currentFile = files[nr];
if (isNotImage(currentFile.type)) {
uploadFile(files, nr + 1, uri);
return;
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
var newHash = xhr.responseText;
console.log("New hash - " + newHash);
if (newHash.length != 64) {
// something wrong
console.log("Something wrong on uploading");
alert('Oh, error on PUT file to BZZ. See log for more information.');
return;
}
insertImage(files, nr, newHash, currentFile.name, function () {
uploadFile(files, nr + 1, "/bzz:/" + newHash + "/");
});
}
};
xhr.open("PUT", uri + "imgs/" + currentFile.name, true);
xhr.setRequestHeader('Content-Type', currentFile.type);
readFile(currentFile, function (result) {
xhr.send(result);
});
}
function readFile(file, onComplete) {
var reader = new FileReader();
reader.onload = function (evt) {
if (onComplete) {
onComplete(evt.target.result)
}
};
reader.readAsArrayBuffer(file);
}
function insertImage(files, nr, newHash, fileName, onComplete) {
// insert image into index
var img = new Image();
img.onload = function () {
var blur = imageToUrl(img, 5, 5);
var thumbData = [];
var thumbSize = 200;
if (img.naturalWidth > img.naturalHeight) {
// landscape thumbnail
var h = img.naturalHeight * thumbSize / img.naturalWidth;
thumbData[0] = imageToUrl(img, thumbSize, h);
thumbData[1] = [thumbSize, h];
} else if (img.naturalWidth < img.naturalHeight) {
// portrait thumbnail
var w = img.naturalWidth * thumbSize / img.naturalHeight;
thumbData[0] = imageToUrl(img, w, thumbSize);
thumbData[1] = [w, thumbSize];
} else {
// square
thumbData[0] = imageToUrl(img, thumbSize, thumbSize);
thumbData[1] = [thumbSize, thumbSize];
}
jQuery('#currentPreview').attr('src', thumbData[0]);
// update index
var imgData = [];
imgData[0] = "imgs/" + fileName;
imgData[1] = [img.naturalWidth, img.naturalHeight];
imgs.data.splice(eidx, 0, {img: imgData, thumb: thumbData, blur: blur});
if (onComplete) {
onComplete();
}
};
img.onerror = function () {
if (onComplete) {
onComplete();
}
};
img.src = "/bzz:/" + newHash + "/imgs/" + fileName;
}
function isNotImage(type) {
var imageType = /^image\//;
return !imageType.test(type);
}
function onUploadingComplete(uri) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
var i = xhr.responseText;
window.location.replace("/bzz:/" + i + "/");
}
};
sendImages(xhr, uri);
}
function handleFiles(files) {
uploadFile(files, 0, "");
}
function sendImages(xhr, uri) {
// set up request
xhr.open("PUT", uri + "data.json", true);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
// send the collected data as JSON
xhr.send(JSON.stringify(imgs));
}
// do it because I love jQuery
function jqueryInit() {
// setup upload file selector
var fileElem = jQuery("#fileElem");
jQuery("#fileSelect").on("click", function (e) {
if (fileElem) {
fileElem.click();
}
e.preventDefault();
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

View file

@ -289,3 +289,7 @@ img
{ {
background: url(cut-mov.png) top left repeat-y, url(cut-mov.png) top right repeat-y; background: url(cut-mov.png) top left repeat-y, url(cut-mov.png) top right repeat-y;
} }
#currentPreview {
max-height: 120px;
}

View file

@ -1,20 +1,26 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1"/>
<link rel="shortcut icon" href="images/favicon.ico"/>
<script src="jquery.js"></script>
<script>
$.noConflict();
</script>
<script src="mootools-core-1.4.js" type="text/javascript"></script> <script src="mootools-core-1.4.js" type="text/javascript"></script>
<script src="mootools-more-1.4.js" type="text/javascript"></script> <script src="mootools-more-1.4.js" type="text/javascript"></script>
<script src="mootools-idle.js" type="text/javascript"></script> <script src="mootools-idle.js" type="text/javascript"></script>
<script src="mootools-mooswipe.js" type="text/javascript"></script> <script src="mootools-mooswipe.js" type="text/javascript"></script>
<script src="file-manager.js" type="text/javascript"></script>
<script src="index.js" type="text/javascript"></script> <script src="index.js" type="text/javascript"></script>
<link href="index.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<noscript>
<h2>Frak! JavaScript required :'(</h2>
</noscript>
<div id="gallery"></div>
</body>
</html>
<link href="index.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<noscript>
<h2>Frak! JavaScript required :'(</h2>
</noscript>
<div id="gallery"></div>
</body>
</html>

View file

@ -2,7 +2,8 @@
// Copyright(c) 2003-2014 by wave++ "Yuri D'Elia" <wavexx@thregr.org> // Copyright(c) 2003-2014 by wave++ "Yuri D'Elia" <wavexx@thregr.org>
// Distributed under GPL2 (see COPYING) WITHOUT ANY WARRANTY. // Distributed under GPL2 (see COPYING) WITHOUT ANY WARRANTY.
var datafile = 'data.json'; var datafile = 'data.json';
var padding = 22; var padding = 100;
var marginTop = 50;
var duration = 500; var duration = 500;
var thrdelay = 1500; var thrdelay = 1500;
var hidedelay = 3000; var hidedelay = 3000;
@ -41,6 +42,7 @@ var eback; // background
var enoise; // additive noise var enoise; // additive noise
var eflash; // flashing object var eflash; // flashing object
var ehdr; // header var ehdr; // header
var progress; // progress
var elist; // thumbnail list var elist; // thumbnail list
var fscr; // thumbnail list scroll fx var fscr; // thumbnail list scroll fx
var econt; // picture container var econt; // picture container
@ -231,7 +233,7 @@ function resizeMainImg(img)
img.setStyles( img.setStyles(
{ {
'position': 'absolute', 'position': 'absolute',
'top': contSize.y / 2 - img.height / 2, 'top': (contSize.y / 2 - img.height / 2) + marginTop,
'left': contSize.x / 2 - img.width / 2 'left': contSize.x / 2 - img.width / 2
}); });
} }
@ -264,15 +266,6 @@ function centerThumb(duration)
fscr = new Fx.Scroll(elist, { duration: duration }).start(x, y); fscr = new Fx.Scroll(elist, { duration: duration }).start(x, y);
} }
function sendImgs(xhr, uri) {
// set up request
xhr.open("PUT", uri + "data.json", true);
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
// send the collected data as JSON
xhr.send(JSON.stringify(imgs));
}
function imageToUrl(img, w, h) { function imageToUrl(img, w, h) {
var can = document.createElement('canvas'); var can = document.createElement('canvas');
can.width = w; can.width = w;
@ -282,70 +275,6 @@ function imageToUrl(img, w, h) {
return can.toDataURL(); return can.toDataURL();
} }
function uploadFile(files, nr, uri) {
if(files.length <= nr) {
if(uri != "") {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { if (xhr.readyState === 4) {
var i = xhr.responseText;
window.location.replace("/" + i + "/");
}};
sendImgs(xhr, uri);
}
return;
}
var imageType = /^image\//;
var file = files[nr];
if(!imageType.test(file.type)) {
uploadFile(files, nr + 1, uri);
return;
}
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { if (xhr.readyState === 4) {
var i = xhr.responseText;
// insert image into index
var img = new Image();
img.onload = function() {
var blur = imageToUrl(img, 5, 5);
var thumbData = [];
var thumbSize = 200;
if(img.naturalWidth > img.naturalHeight) {
// landscape thumbnail
var h = img.naturalHeight * thumbSize / img.naturalWidth;
thumbData[0] = imageToUrl(img, thumbSize, h);
thumbData[1] = [thumbSize, h];
} else {
// portrait thumbnail
var w = img.naturalWidth * thumbSize / img.naturalHeight;
thumbData[0] = imageToUrl(img, w, thumbsize);
thumbData[1] = [w, thumbSize];
}
// update index
var imgData = [];
imgData[0] = "imgs/" + file.name;
imgData[1] = [img.naturalWidth, img.naturalHeight];
imgs.data.splice(eidx, 0, {img: imgData, thumb: thumbData, blur: blur});
uploadFile(files, nr + 1, "/" + i + "/");
}
img.src = "/" + i + "/imgs/" + file.name;
return;
}};
xhr.open("PUT", uri + "imgs/" + file.name, true);
xhr.setRequestHeader('Content-Type', file.type);
var reader = new FileReader();
reader.onload = function(evt) {
xhr.send(evt.target.result);
};
reader.readAsArrayBuffer(file);
}
function handleFiles(files) {
uploadFile(files, 0, "");
}
function deleteImg() function deleteImg()
{ {
if(imgs.data.length < 2) return; // empty albums not allowed if(imgs.data.length < 2) return; // empty albums not allowed
@ -361,13 +290,13 @@ function deleteImg()
var xhrd = new XMLHttpRequest(); var xhrd = new XMLHttpRequest();
xhrd.onreadystatechange = function () { if (xhrd.readyState === 4) { xhrd.onreadystatechange = function () { if (xhrd.readyState === 4) {
var j = xhrd.responseText; 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(); xhrd.send();
}}; }};
sendImgs(xhr, ""); sendImages(xhr, "");
} }
function moveUpDown(off) function moveUpDown(off)
@ -378,9 +307,9 @@ function moveUpDown(off)
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () { if (xhr.readyState === 4) { xhr.onreadystatechange = function () { if (xhr.readyState === 4) {
var i = xhr.responseText; var i = xhr.responseText;
window.location.replace("/" + i + "/#" + (eidx + off)); window.location.replace("/bzz:/" + i + "/#" + (eidx + off));
}}; }};
sendImgs(xhr, ""); sendImages(xhr, "");
} }
function moveUp() function moveUp()
@ -435,15 +364,8 @@ function onMainReady()
ehdr.set('html', dsc.join(' ')); ehdr.set('html', dsc.join(' '));
ehdr.setStyle('display', (dsc.length? 'block': 'none')); ehdr.setStyle('display', (dsc.length? 'block': 'none'));
// setup upload file selector progress.set('html', '<img id="currentPreview">');
var fileSelect = document.getElementById("fileSelect"), progress.set('style', 'text-align: center; padding-top: 20px');
fileElem = document.getElementById("fileElem");
fileSelect.addEventListener("click", function (e) {
if (fileElem) {
fileElem.click();
}
e.preventDefault(); // prevent navigation to "#"
}, false);
// complete thumbnails // complete thumbnails
var d = duration; var d = duration;
@ -494,6 +416,8 @@ function onMainReady()
var data = imgs.data[eidx + 1]; var data = imgs.data[eidx + 1];
Asset.images([data.img[0], data.blur]); Asset.images([data.img[0], data.blur]);
} }
jqueryInit();
} }
function showThrobber() function showThrobber()
@ -677,6 +601,9 @@ function initGallery(data)
ehdr = new Element('div', { id: 'header' }); ehdr = new Element('div', { id: 'header' });
ehdr.inject(econt); ehdr.inject(econt);
progress = new Element('div', { id: 'progress' });
progress.inject(econt);
elist = new Element('div', { id: 'list' }); elist = new Element('div', { id: 'list' });
elist.inject(emain); elist.inject(emain);

4
swarm/examples/album/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -6,10 +6,21 @@
navigator.registerProtocolHandler("bzz", navigator.registerProtocolHandler("bzz",
"http://localhost:8500/%s", "http://localhost:8500/%s",
"Swarm handler"); "Swarm handler");
navigator.registerProtocolHandler("bzzi",
"http://localhost:8500/%s",
"Swarm immutable handler");
navigator.registerProtocolHandler("bzzr",
"http://localhost:8500/%s",
"Swarm raw handler");
</script> </script>
</head> </head>
<body> <body>
<h1>Register Swarm protocol handler</h1> <h1>Register Swarm protocol handler</h1>
<p>This web page will install a web protocol handler for the <code>bzz:</code> protocol.</p> <p>This web page will install a web protocol handler for
<code>bzz:</code>,
<code>bzzi:</code> and
<code>bzzr:</code>
protocols.
</p>
</body> </body>
</html> </html>

View file

@ -5,9 +5,9 @@ import (
"encoding/binary" "encoding/binary"
"time" "time"
"github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/swarm/storage"
) )
// Handler for storage/retrieval related protocol requests // Handler for storage/retrieval related protocol requests
@ -51,6 +51,7 @@ func (self *Depo) HandleUnsyncedKeysMsg(req *unsyncedKeysMsgData, p *peer) error
if err != nil { if err != nil {
return err return err
} }
// set peers state to persist
p.syncState = req.State p.syncState = req.State
return nil return nil
} }
@ -124,7 +125,11 @@ func (self *Depo) HandleRetrieveRequestMsg(req *retrieveRequestMsgData, p *peer)
req.from = p req.from = p
// swap - record credit for 1 request // swap - record credit for 1 request
// note that only charge actual reqsearches // 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) glog.V(logger.Warn).Infof("[BZZ] Depo.HandleRetrieveRequest: %v - cannot process request: %v", req.Key.Log(), err)
return return
} }

View file

@ -4,9 +4,9 @@ import (
"math/rand" "math/rand"
"time" "time"
"github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/swarm/storage"
) )
const requesterCount = 3 const requesterCount = 3
@ -18,7 +18,6 @@ via the native bzz protocol
which uses an MSB logarithmic distance-based semi-permanent Kademlia table for which uses an MSB logarithmic distance-based semi-permanent Kademlia table for
* recursive forwarding style routing for retrieval * recursive forwarding style routing for retrieval
* smart syncronisation * smart syncronisation
* TODO: beeline delivery, IPFS, IPΞS
*/ */
type forwarder struct { type forwarder struct {
@ -42,26 +41,30 @@ var searchTimeout = 3 * time.Second
func (self *forwarder) Retrieve(chunk *storage.Chunk) { func (self *forwarder) Retrieve(chunk *storage.Chunk) {
peers := self.hive.getPeers(chunk.Key, 0) 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)) 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 { for _, p := range peers {
glog.V(logger.Detail).Infof("[BZZ] forwarder.Retrieve: sending retrieveRequest %v to peer [%v]", chunk.Key.Log(), p) 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 _, recipients := range chunk.Req.Requesters {
for _, recipient := range recipients { for _, recipient := range recipients {
req := recipient.(*retrieveRequestMsgData) req := recipient.(*retrieveRequestMsgData)
if req.from.Addr() == p.Addr() { if req.from.Addr() == p.Addr() {
break OUT continue OUT
} }
} }
} }
if req != nil { req := &retrieveRequestMsgData{
if err := p.swap.Add(-1); err == nil { Key: chunk.Key,
p.retrieve(req) Id: generateId(),
break
} else {
glog.V(logger.Warn).Infof("[BZZ] forwarder.Retrieve: unable to send retrieveRequest to peer [%v]: %v", chunk.Key.Log(), err)
}
} }
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) source = chunk.Source.(*peer)
} }
for _, p := range self.hive.getPeers(chunk.Key, 0) { 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++ n++
Deliver(p, msg, PropagateReq) 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 // 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 { for id, r := range requesters {
req = r.(*retrieveRequestMsgData) req = r.(*retrieveRequestMsgData)
if req.timeout == nil || req.timeout.After(time.Now()) { 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) msg.Id = uint64(id)
Deliver(req.from, msg, DeliverReq) Deliver(req.from, msg, DeliverReq)
n++ 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)
} }
} }

View file

@ -7,10 +7,10 @@ import (
"time" "time"
"github.com/ethereum/go-ethereum/common" "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"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/swarm/network/kademlia"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
) )
@ -32,13 +32,19 @@ type Hive struct {
path string path string
toggle chan bool toggle chan bool
more chan bool more chan bool
// for testing only
swapEnabled bool
syncEnabled bool
blockRead bool
blockWrite bool
} }
const ( const (
callInterval = 10000000000 callInterval = 3000000000
bucketSize = 3 // bucketSize = 3
maxProx = 10 // maxProx = 8
proxBinSize = 8 // proxBinSize = 4
) )
type HiveParams struct { type HiveParams struct {
@ -49,9 +55,9 @@ type HiveParams struct {
func NewHiveParams(path string) *HiveParams { func NewHiveParams(path string) *HiveParams {
kad := kademlia.NewKadParams() kad := kademlia.NewKadParams()
kad.BucketSize = bucketSize // kad.BucketSize = bucketSize
kad.MaxProx = maxProx // kad.MaxProx = maxProx
kad.ProxBinSize = proxBinSize // kad.ProxBinSize = proxBinSize
return &HiveParams{ return &HiveParams{
CallInterval: callInterval, 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) kad := kademlia.New(kademlia.Address(addr), params.KadParams)
return &Hive{ return &Hive{
callInterval: params.CallInterval, callInterval: params.CallInterval,
kad: kad, kad: kad,
addr: kad.Addr(), addr: kad.Addr(),
path: params.KadDbPath, 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 // public accessor to the hive base address
func (self *Hive) Addr() kademlia.Address { func (self *Hive) Addr() kademlia.Address {
return self.addr return self.addr
@ -122,7 +146,7 @@ func (self *Hive) Start(id discover.NodeID, listenAddr func() string, connectPee
} else { } else {
self.toggle <- false 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 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 // wake state is toggled by writing to self.toggle
// it restarts if the table becomes non-full again due to disconnections // it restarts if the table becomes non-full again due to disconnections
func (self *Hive) keepAlive() { func (self *Hive) keepAlive() {
var alarm <-chan time.Time alarm := time.NewTicker(time.Duration(self.callInterval)).C
for { for {
select { select {
case <-alarm: case <-alarm:
@ -185,14 +209,14 @@ func (self *Hive) addPeer(p *peer) {
// called after peer disconnected // called after peer disconnected
func (self *Hive) removePeer(p *peer) { func (self *Hive) removePeer(p *peer) {
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: bee %v gone offline", p) glog.V(logger.Debug).Infof("[BZZ] KΛÐΞMLIΛ hive: bee %v removed", p)
self.kad.Off(p, saveSync) self.kad.Off(p, saveSync)
select { select {
case self.more <- true: case self.more <- true:
default: default:
} }
if self.kad.Count() == 0 { if self.kad.Count() == 0 {
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: empty, all bees gone", p) glog.V(logger.Debug).Infof("[BZZ] KΛÐΞMLIΛ hive: empty, all bees gone")
} }
} }
@ -208,7 +232,7 @@ func (self *Hive) getPeers(target storage.Key, max int) (peers []*peer) {
// disconnects all the peers // disconnects all the peers
func (self *Hive) DropAll() { func (self *Hive) DropAll() {
glog.V(logger.Detail).Infof("[BZZ] KΛÐΞMLIΛ hive: dropping all bees") glog.V(logger.Info).Infof("[BZZ] KΛÐΞMLIΛ hive: dropping all bees")
for _, node := range self.kad.FindClosest(kademlia.Address{}, 0) { for _, node := range self.kad.FindClosest(kademlia.Address{}, 0) {
node.Drop() node.Drop()
} }
@ -260,17 +284,22 @@ func (self *peer) LastActive() time.Time {
// reads the serialised form of sync state persisted as the 'Meta' attribute // reads the serialised form of sync state persisted as the 'Meta' attribute
// and sets the decoded syncState on the online node // and sets the decoded syncState on the online node
func loadSync(record *kademlia.NodeRecord, node kademlia.Node) error { func loadSync(record *kademlia.NodeRecord, node kademlia.Node) error {
if p, ok := node.(*peer); ok { p, ok := node.(*peer)
if record.Meta == nil { if !ok {
glog.V(logger.Debug).Infof("no sync state for node record %v", record) return fmt.Errorf("invalid type")
return nil
}
state, err := decodeSync(record.Meta)
glog.V(logger.Debug).Infof("sync state for node record %v: %s -> %v", record, string(*(record.Meta)), state)
p.syncState = state
return err
} }
return fmt.Errorf("invalid type") if record.Meta == nil {
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.Detail).Infof("sync state for node record %v read from Meta: %s", record, string(*(record.Meta)))
p.syncState = state
return err
} }
// callback when saving a sync state // callback when saving a sync state
@ -281,8 +310,7 @@ func saveSync(record *kademlia.NodeRecord, node kademlia.Node) {
glog.V(logger.Warn).Infof("error saving sync state for %v: %v", node, err) glog.V(logger.Warn).Infof("error saving sync state for %v: %v", node, err)
return return
} }
glog.V(logger.Warn).Infof("saving sync state for %v: %s", node, string(*meta)) glog.V(logger.Detail).Infof("saved sync state for %v: %s", node, string(*meta))
record.Meta = meta record.Meta = meta
} }
} }
@ -320,3 +348,7 @@ func (self *Hive) peers(req *retrieveRequestMsgData) {
} }
} }
} }
func (self *Hive) String() string {
return self.kad.String()
}

View file

@ -130,7 +130,9 @@ func (self *KadDb) add(nrs []*NodeRecord, proximityBin func(Address) int) {
n++ n++
} }
} }
glog.V(logger.Detail).Infof("[KΛÐ]: received %d node records, added %d new", len(nrs), n) if n > 0 {
glog.V(logger.Debug).Infof("[KΛÐ]: %d/%d node records (new/known)", n, len(nrs))
}
} }
/* /*
@ -145,7 +147,8 @@ We check for missing online nodes in the buckets for 1 upto Max BucketSize round
On each round we proceed from the low to high proximity order buckets. On each round we proceed from the low to high proximity order buckets.
If the number of active nodes (=connected peers) is < rounds, then start looking If the number of active nodes (=connected peers) is < rounds, then start looking
for a known candidate. To determine if there is a candidate to recommend the for a known candidate. To determine if there is a candidate to recommend the
node record database row corresponding to the bucket is checked.a node record database row corresponding to the bucket is checked.
If the row cursor is on position i, the ith element in the row is chosen. If the row cursor is on position i, the ith element in the row is chosen.
If the record is scheduled not to be retried before NOW, the next element is taken. If the record is scheduled not to be retried before NOW, the next element is taken.
If the record is scheduled can be retried, it is set as checked, scheduled for If the record is scheduled can be retried, it is set as checked, scheduled for

View file

@ -12,14 +12,16 @@ import (
) )
const ( const (
bucketSize = 20 bucketSize = 3
maxProx = 255 proxBinSize = 4
maxProx = 8
connRetryExp = 2 connRetryExp = 2
) )
var ( var (
purgeInterval = 42 * time.Hour purgeInterval = 42 * time.Hour
initialRetryInterval = 42 * 100 * time.Millisecond initialRetryInterval = 42 * 100 * time.Millisecond
maxIdleInterval = 42 * 10 * time.Second
) )
type KadParams struct { type KadParams struct {
@ -35,7 +37,7 @@ type KadParams struct {
func NewKadParams() *KadParams { func NewKadParams() *KadParams {
return &KadParams{ return &KadParams{
MaxProx: maxProx, MaxProx: maxProx,
ProxBinSize: bucketSize, ProxBinSize: proxBinSize,
BucketSize: bucketSize, BucketSize: bucketSize,
PurgeInterval: purgeInterval, PurgeInterval: purgeInterval,
InitialRetryInterval: initialRetryInterval, InitialRetryInterval: initialRetryInterval,
@ -72,6 +74,8 @@ func New(addr Address, params *KadParams) *Kademlia {
} }
glog.V(logger.Info).Infof("[KΛÐ] base address %v", addr) glog.V(logger.Info).Infof("[KΛÐ] base address %v", addr)
// ! temporary hack fixme:
params.ProxBinSize = 4
return &Kademlia{ return &Kademlia{
addr: addr, addr: addr,
KadParams: params, KadParams: params,
@ -100,41 +104,45 @@ func (self *Kademlia) DBCount() int {
// On is the entry point called when a new nodes is added // 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) // 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) { 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()) index := self.proximityBin(node.Addr())
record := self.db.findOrCreate(index, node.Addr(), node.Url()) record := self.db.findOrCreate(index, node.Addr(), node.Url())
// callback on add node // callback on add node
// setting the node on the record, set it checked (for connectivity) // setting the node on the record, set it checked (for connectivity)
record.node = node record.node = node
glog.V(logger.Info).Infof("[KΛÐ]: add node record %v with node %v", record, node)
if cb != nil { if cb != nil {
err = cb(record, node) err = cb(record, node)
glog.V(logger.Info).Infof("[KΛÐ]: cb(%v, %v) ->%v", record, node, err) glog.V(logger.Detail).Infof("[KΛÐ]: cb(%v, %v) ->%v", record, node, err)
if err != nil { if err != nil {
return fmt.Errorf("node %v not added: %v", node.Addr(), err) return fmt.Errorf("unable to add node %v, callback error: %v", node.Addr(), err)
} }
glog.V(logger.Info).Infof("[KΛÐ]: add node record %v with node %v", record, node)
} }
record.connected = true record.connected = true
defer self.lock.Unlock()
self.lock.Lock()
// insert in kademlia table of active nodes // insert in kademlia table of active nodes
bucket := self.buckets[index] bucket := self.buckets[index]
// if bucket is full insertion replaces the worst node // if bucket is full insertion replaces the worst node
// TODO: give priority to peers with active traffic // TODO: give priority to peers with active traffic
if worst, pos := bucket.insert(node); worst != nil { replaced, err := bucket.insert(node)
glog.V(logger.Info).Infof("[KΛÐ]: replace node %v (%d) with node %v", worst, pos, node) if err != nil {
glog.V(logger.Debug).Infof("[KΛÐ]: node %v not needed: %v", node, err)
return err
// no prox adjustment needed // no prox adjustment needed
// do not change count // do not change count
} else {
glog.V(logger.Info).Infof("[KΛÐ]: add node %v to table", node)
self.count++
self.adjustProxMore(index)
} }
if replaced != nil {
glog.V(logger.Debug).Infof("[KΛÐ]: node %v replaced by %v ", replaced, node)
return
}
// new node added
glog.V(logger.Info).Infof("[KΛÐ]: add node %v to table", node)
self.count++
self.setProxLimit(index, false)
return return
} }
// is the entrypoint called when a node is taken offline // is the entrypoint called when a node is taken offline
@ -161,7 +169,8 @@ func (self *Kademlia) Off(node Node, cb func(*NodeRecord, Node)) (err error) {
if len(bucket.nodes) < bucket.size { if len(bucket.nodes) < bucket.size {
err = fmt.Errorf("insufficient nodes (%v) in bucket %v", len(bucket.nodes), index) 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()] r := self.db.index[node.Addr()]
// callback on remove // callback on remove
@ -174,49 +183,40 @@ func (self *Kademlia) Off(node Node, cb func(*NodeRecord, Node)) (err error) {
return return
} }
// proxLimit is dynamically adjusted so that 1) there is no // proxLimit is dynamically adjusted so that
// empty buckets in bin < proxLimit and 2) the sum of all items sare the maximum // 1) there is no empty buckets in bin < proxLimit and
// possible but lower than ProxBinSize // 2) the sum of all items sare the maximpossible but lower than ProxBinSize
// adjust Prox (proxLimit and proxSize after an insertion of add nodes into bucket r) // adjust Prox (proxLimit and proxSize after an insertion/removal of nodes)
func (self *Kademlia) adjustProxMore(r int) { // caller holds the lock
if r >= self.proxLimit { func (self *Kademlia) setProxLimit(r int, off bool) {
exLimit := self.proxLimit // glog.V(logger.Info).Infof("[KΛÐ]: adjust proxbin for (bin: %v, off: %v)", r, off)
exSize := self.proxSize if r < self.proxLimit && len(self.buckets[r].nodes) > 0 {
self.proxSize++ return
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)
} }
} glog.V(logger.Detail).Infof("[KΛÐ]: set proxbin (size: %v, limit: %v, bin: %v, off: %v)", self.proxSize, self.proxLimit, r, off)
if off {
func (self *Kademlia) adjustProxLess(r int) {
exLimit := self.proxLimit
exSize := self.proxSize
if r >= self.proxLimit {
self.proxSize-- self.proxSize--
} for (self.proxSize < self.ProxBinSize || r < self.proxLimit) &&
self.proxLimit > 0 {
if r < self.proxLimit && len(self.buckets[r].nodes) == 0 { //
for i := self.proxLimit - 1; i > r; i-- { self.proxLimit--
self.proxSize += len(self.buckets[i].nodes) 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 glog.V(logger.Detail).Infof("%v", self)
} else if self.proxLimit > 0 && r >= self.proxLimit-1 { return
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
} }
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 +251,7 @@ func (self *Kademlia) FindClosest(target Address, max int) []Node {
r.push(bucket[i], limit) r.push(bucket[i], limit)
n++ n++
} }
if max == 0 && start <= index && (n > 0 || start == 0) || if max == 0 && start <= index && (n > 0 || start == 0) || max > 0 && down && start <= index && (n >= limit || n == self.count || start == 0) {
max > 0 && down && start <= index && (n >= limit || n == self.count || start == 0) {
break break
} }
if down { if down {
@ -335,17 +334,13 @@ func (h *nodesByDistance) push(node Node, max int) {
// insert adds a peer to a bucket either by appending to existing items if // insert adds a peer to a bucket either by appending to existing items if
// bucket length does not exceed bucketSize, or by replacing the worst // bucket length does not exceed bucketSize, or by replacing the worst
// Node in the bucket // Node in the bucket
func (self *bucket) insert(node Node) (dropped Node, pos int) { func (self *bucket) insert(node Node) (replaced Node, err error) {
self.lock.Lock() self.lock.Lock()
defer self.lock.Unlock() defer self.lock.Unlock()
if len(self.nodes) >= self.size { // >= allows us to add peers beyond the bucketsize limitation if len(self.nodes) >= self.size { // >= allows us to add peers beyond the bucketsize limitation
dropped, pos = self.worstNode() // dev p2p kicks out nodes idle for > 30 s, so here we never replace nodes if
if dropped != nil { // bucket is full
self.nodes[pos] = node return nil, fmt.Errorf("bucket full")
glog.V(logger.Info).Infof("[KΛÐ] dropping node %v (%d)", dropped, pos)
dropped.Drop()
return
}
} }
self.nodes = append(self.nodes, node) self.nodes = append(self.nodes, node)
return return
@ -357,20 +352,6 @@ func (self *bucket) length(node Node) int {
return len(self.nodes) return len(self.nodes)
} }
// worst expunges the single worst node in a row, where worst entry is the node
// that has been inactive for the longests time
func (self *bucket) worstNode() (node Node, pos int) {
var oldest time.Time
for p, n := range self.nodes {
if (oldest == time.Time{}) || !oldest.Before(n.LastActive()) {
oldest = n.LastActive()
node = n
pos = p
}
}
return
}
/* /*
Taking the proximity order relative to a fix point x classifies the points in Taking the proximity order relative to a fix point x classifies the points in
the space (n byte long byte sequences) into bins. Items in each are at the space (n byte long byte sequences) into bins. Items in each are at
@ -415,10 +396,12 @@ func (self *Kademlia) Load(path string, cb func(*NodeRecord, Node) error) (err e
} }
// kademlia table + kaddb table displayed with ascii // kademlia table + kaddb table displayed with ascii
// callerholds the lock
func (self *Kademlia) String() string { func (self *Kademlia) String() string {
var rows []string var rows []string
rows = append(rows, "=========================================================================") rows = append(rows, "=========================================================================")
rows = append(rows, fmt.Sprintf("KΛÐΞMLIΛ hive: queen's address: %v, population: %d (%d)", self.addr, self.Count(), self.DBCount()))
rows = append(rows, fmt.Sprintf("%v : MaxProx: %d, ProxBinSize: %d, BucketSize: %d, proxLimit: %d, proxSize: %d", time.Now(), self.MaxProx, self.ProxBinSize, self.BucketSize, self.proxLimit, self.proxSize)) rows = append(rows, fmt.Sprintf("%v : MaxProx: %d, ProxBinSize: %d, BucketSize: %d, proxLimit: %d, proxSize: %d", time.Now(), self.MaxProx, self.ProxBinSize, self.BucketSize, self.proxLimit, self.proxSize))
for i, b := range self.buckets { for i, b := range self.buckets {

View file

@ -5,10 +5,11 @@ import (
"net" "net"
"time" "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/services/swap"
"github.com/ethereum/go-ethereum/swarm/storage" "github.com/ethereum/go-ethereum/swarm/storage"
"github.com/ethereum/go-ethereum/common/chequebook"
"github.com/ethereum/go-ethereum/common/kademlia"
) )
/* /*
@ -76,7 +77,11 @@ func (self storeRequestMsgData) String() string {
} else { } else {
from = self.from.Addr().String() from = self.from.Addr().String()
} }
return fmt.Sprintf("from: %v, Key: %v; ID: %v, requestTimeout: %v, storageTimeout: %v, SData %x", from, self.Key, self.Id, self.requestTimeout, self.storageTimeout, self.SData[:10]) end := len(self.SData)
if len(self.SData) > 10 {
end = 10
}
return fmt.Sprintf("from: %v, Key: %v; ID: %v, requestTimeout: %v, storageTimeout: %v, SData %x", from, self.Key, self.Id, self.requestTimeout, self.storageTimeout, self.SData[:end])
} }
/* /*
@ -108,7 +113,7 @@ type retrieveRequestMsgData struct {
MaxSize uint64 // maximum size of delivery accepted MaxSize uint64 // maximum size of delivery accepted
MaxPeers uint64 // maximum number of peers returned MaxPeers uint64 // maximum number of peers returned
Timeout uint64 // the longest time we are expecting a response Timeout uint64 // the longest time we are expecting a response
timeout *time.Time // [not serialied]} timeout *time.Time // [not serialied]
from *peer // from *peer //
} }
@ -160,7 +165,9 @@ type peerAddr struct {
// peerAddr pretty prints as enode // peerAddr pretty prints as enode
func (self peerAddr) String() string { 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 +238,7 @@ peer/protocol instance when the node is registered by hive as online{
*/ */
type syncRequestMsgData struct { type syncRequestMsgData struct {
SyncState *syncState `rlp:"nil"` SyncState *syncState `rlp:"nil" `
} }
func (self *syncRequestMsgData) String() string { func (self *syncRequestMsgData) String() string {

View file

@ -1,33 +1,35 @@
package network 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 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 The bzz protocol component speaks the bzz protocol
* encoding and decoding requests for storage and retrieval * handle the protocol handshake
* handling the s§protocol handshake * register peers in the KΛÐΞMLIΛ table via the hive logistic manager
* dispaching to netstore for handling the DHT logic * dispatch to hive for handling the DHT logic
* registering peers in the KΛÐΞMLIΛ table via the hive logistic manager * encode and decode requests for storage and retrieval
* handling sync protocol messages via the syncer * handle sync protocol messages via the syncer
* talks the SWAP payent protocol (swap accounting is done within NetStore) * talks the SWAP payment protocol (swap accounting is done within NetStore)
*/ */
import ( import (
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
"time"
bzzswap "github.com/ethereum/go-ethereum/swarm/services/swap" "github.com/ethereum/go-ethereum/accounts/abi/bind"
"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/errs"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/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 ( const (
@ -67,21 +69,22 @@ type bzz struct {
selfID discover.NodeID // peer's node id used in peer advertising in handshake selfID discover.NodeID // peer's node id used in peer advertising in handshake
key storage.Key // baseaddress as storage.Key key storage.Key // baseaddress as storage.Key
storage StorageHandler // handler storage/retrieval related requests coming via the bzz wire protocol 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 dbAccess *DbAccess // access to db storage counter and iterator for syncing
requestDb *storage.LDBDatabase // db to persist backlog of deliveries to aid syncing requestDb *storage.LDBDatabase // db to persist backlog of deliveries to aid syncing
remoteAddr *peerAddr // remote peers address remoteAddr *peerAddr // remote peers address
peer *p2p.Peer // the p2p peer object peer *p2p.Peer // the p2p peer object
rw p2p.MsgReadWriter // messageReadWriter to send messages to rw p2p.MsgReadWriter // messageReadWriter to send messages to
errors *errs.Errors // errors table errors *errs.Errors // errors table
backend bind.Backend
swap *swap.Swap // swap instance for the peer connection swap *swap.Swap // swap instance for the peer connection
swapParams *bzzswap.SwapParams // swap settings both local and remote 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 syncer *syncer // syncer instance for the peer connection
syncParams *SyncParams // syncer params syncParams *SyncParams // syncer params
syncState *syncState // outgoing syncronisation state (contains reference to remote peers db counter) 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 // interface type for handler of storage/retrieval related requests coming
@ -105,7 +108,7 @@ on each peer connection
The Run function of the Bzz protocol class creates a bzz instance The Run function of the Bzz protocol class creates a bzz instance
which will represent the peer for the swarm hive and all peer-aware components which will represent the peer for the swarm hive and all peer-aware components
*/ */
func Bzz(cloud StorageHandler, hive *Hive, dbaccess *DbAccess, sp *bzzswap.SwapParams, sy *SyncParams) (p2p.Protocol, error) { func Bzz(cloud StorageHandler, backend bind.Backend, hive *Hive, dbaccess *DbAccess, sp *bzzswap.SwapParams, sy *SyncParams) (p2p.Protocol, error) {
// a single global request db is created for all peer connections // a single global request db is created for all peer connections
// this is to persist delivery backlog and aid syncronisation // this is to persist delivery backlog and aid syncronisation
@ -119,7 +122,7 @@ func Bzz(cloud StorageHandler, hive *Hive, dbaccess *DbAccess, sp *bzzswap.SwapP
Version: Version, Version: Version,
Length: ProtocolLength, Length: ProtocolLength,
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
return run(requestDb, cloud, hive, dbaccess, sp, sy, p, rw) return run(requestDb, cloud, backend, hive, dbaccess, sp, sy, p, rw)
}, },
}, nil }, nil
} }
@ -136,10 +139,11 @@ the main protocol loop that
* whenever the loop terminates, the peer will disconnect with Subprotocol error * whenever the loop terminates, the peer will disconnect with Subprotocol error
* whenever handlers return an error the loop terminates * whenever handlers return an error the loop terminates
*/ */
func run(requestDb *storage.LDBDatabase, depo StorageHandler, hive *Hive, dbaccess *DbAccess, sp *bzzswap.SwapParams, sy *SyncParams, p *p2p.Peer, rw p2p.MsgReadWriter) (err error) { func run(requestDb *storage.LDBDatabase, depo StorageHandler, backend bind.Backend, hive *Hive, dbaccess *DbAccess, sp *bzzswap.SwapParams, sy *SyncParams, p *p2p.Peer, rw p2p.MsgReadWriter) (err error) {
self := &bzz{ self := &bzz{
storage: depo, storage: depo,
backend: backend,
hive: hive, hive: hive,
dbAccess: dbaccess, dbAccess: dbaccess,
requestDb: requestDb, requestDb: requestDb,
@ -151,7 +155,7 @@ func run(requestDb *storage.LDBDatabase, depo StorageHandler, hive *Hive, dbacce
}, },
swapParams: sp, swapParams: sp,
syncParams: sy, syncParams: sy,
swapEnabled: true, swapEnabled: hive.swapEnabled,
syncEnabled: true, syncEnabled: true,
} }
@ -174,6 +178,10 @@ func run(requestDb *storage.LDBDatabase, depo StorageHandler, hive *Hive, dbacce
// the main forever loop that handles incoming requests // the main forever loop that handles incoming requests
for { for {
if self.hive.blockRead {
time.Sleep(1 * time.Second)
continue
}
err = self.handle() err = self.handle()
if err != nil { if err != nil {
return return
@ -182,7 +190,7 @@ func run(requestDb *storage.LDBDatabase, depo StorageHandler, hive *Hive, dbacce
return return
} }
// may need to implement protocol drop only? don't want to kick off the peer // TODO: may need to implement protocol drop only? don't want to kick off the peer
// if they are useful for other protocols // if they are useful for other protocols
func (self *bzz) Drop() { func (self *bzz) Drop() {
self.peer.Disconnect(p2p.DiscSubprotocolError) self.peer.Disconnect(p2p.DiscSubprotocolError)
@ -213,7 +221,7 @@ func (self *bzz) handle() error {
// store requests are dispatched to netStore // store requests are dispatched to netStore
var req storeRequestMsgData var req storeRequestMsgData
if err := msg.Decode(&req); err != nil { if err := msg.Decode(&req); err != nil {
return self.protoError(ErrDecode, "msg %v: %v", msg, err) return self.protoError(ErrDecode, "<- %v: %v", msg, err)
} }
glog.V(logger.Debug).Infof("[BZZ] incoming store request: %s", req.String()) glog.V(logger.Debug).Infof("[BZZ] incoming store request: %s", req.String())
// swap accounting is done within forwarding // swap accounting is done within forwarding
@ -223,7 +231,7 @@ func (self *bzz) handle() error {
// retrieve Requests are dispatched to netStore // retrieve Requests are dispatched to netStore
var req retrieveRequestMsgData var req retrieveRequestMsgData
if err := msg.Decode(&req); err != nil { if err := msg.Decode(&req); err != nil {
return self.protoError(ErrDecode, "->msg %v: %v", msg, err) return self.protoError(ErrDecode, "<- %v: %v", msg, err)
} }
req.from = &peer{bzz: self} req.from = &peer{bzz: self}
// if request is lookup and not to be delivered // if request is lookup and not to be delivered
@ -243,55 +251,55 @@ func (self *bzz) handle() error {
// dispatches new peer data to the hive that adds them to KADDB // dispatches new peer data to the hive that adds them to KADDB
var req peersMsgData var req peersMsgData
if err := msg.Decode(&req); err != nil { if err := msg.Decode(&req); err != nil {
return self.protoError(ErrDecode, "->msg %v: %v", msg, err) return self.protoError(ErrDecode, "<- %v: %v", msg, err)
} }
req.from = &peer{bzz: self} req.from = &peer{bzz: self}
glog.V(logger.Debug).Infof("[BZZ] incoming peer addresses: %v", req) glog.V(logger.Debug).Infof("[BZZ] <- peer addresses: %v", req)
self.hive.HandlePeersMsg(&req, &peer{bzz: self}) self.hive.HandlePeersMsg(&req, &peer{bzz: self})
case syncRequestMsg: case syncRequestMsg:
var req syncRequestMsgData var req syncRequestMsgData
if err := msg.Decode(&req); err != nil { if err := msg.Decode(&req); err != nil {
return self.protoError(ErrDecode, "->msg %v: %v", msg, err) return self.protoError(ErrDecode, "<- %v: %v", msg, err)
} }
glog.V(logger.Debug).Infof("[BZZ] sync request received: %v", req) glog.V(logger.Debug).Infof("[BZZ] <- sync request: %v", req)
self.sync(req.SyncState) self.sync(req.SyncState)
case unsyncedKeysMsg: case unsyncedKeysMsg:
// coming from parent node offering // coming from parent node offering
var req unsyncedKeysMsgData var req unsyncedKeysMsgData
if err := msg.Decode(&req); err != nil { if err := msg.Decode(&req); err != nil {
return self.protoError(ErrDecode, "->msg %v: %v", msg, err) return self.protoError(ErrDecode, "<- %v: %v", msg, err)
} }
glog.V(logger.Debug).Infof("[BZZ] incoming unsynced keys msg: %s", req.String()) glog.V(logger.Debug).Infof("[BZZ] <- unsynced keys : %s", req.String())
err := self.storage.HandleUnsyncedKeysMsg(&req, &peer{bzz: self}) err := self.storage.HandleUnsyncedKeysMsg(&req, &peer{bzz: self})
if err != nil { if err != nil {
return self.protoError(ErrDecode, "->msg %v: %v", msg, err) return self.protoError(ErrDecode, "<- %v: %v", msg, err)
} }
// set peers state to persist
self.syncState = req.State
case deliveryRequestMsg: case deliveryRequestMsg:
// response to syncKeysMsg hashes filtered not existing in db // response to syncKeysMsg hashes filtered not existing in db
// also relays the last synced state to the source // also relays the last synced state to the source
var req deliveryRequestMsgData var req deliveryRequestMsgData
if err := msg.Decode(&req); err != nil { if err := msg.Decode(&req); err != nil {
return self.protoError(ErrDecode, "->msg %v: %v", msg, err) return self.protoError(ErrDecode, "<-msg %v: %v", msg, err)
} }
glog.V(logger.Debug).Infof("[BZZ] incoming delivery request: %s", req.String()) glog.V(logger.Debug).Infof("[BZZ] <- delivery request: %s", req.String())
err := self.storage.HandleDeliveryRequestMsg(&req, &peer{bzz: self}) err := self.storage.HandleDeliveryRequestMsg(&req, &peer{bzz: self})
if err != nil { if err != nil {
return self.protoError(ErrDecode, "->msg %v: %v", msg, err) return self.protoError(ErrDecode, "<- %v: %v", msg, err)
} }
case paymentMsg: case paymentMsg:
// swap protocol message for payment, Units paid for, Cheque paid with // swap protocol message for payment, Units paid for, Cheque paid with
var req paymentMsgData if self.swapEnabled {
if err := msg.Decode(&req); err != nil { var req paymentMsgData
return self.protoError(ErrDecode, "->msg %v: %v", msg, err) if err := msg.Decode(&req); err != nil {
return self.protoError(ErrDecode, "<- %v: %v", msg, err)
}
glog.V(logger.Debug).Infof("[BZZ] <- 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: default:
// no other message is allowed // no other message is allowed
@ -335,7 +343,7 @@ func (self *bzz) handleStatus() (err error) {
var status statusMsgData var status statusMsgData
if err := msg.Decode(&status); err != nil { if err := msg.Decode(&status); err != nil {
return self.protoError(ErrDecode, "msg %v: %v", msg, err) return self.protoError(ErrDecode, " %v: %v", msg, err)
} }
if status.NetworkId != NetworkId { if status.NetworkId != NetworkId {
@ -351,7 +359,7 @@ func (self *bzz) handleStatus() (err error) {
if self.swapEnabled { if self.swapEnabled {
// set remote profile for accounting // set remote profile for accounting
self.swap, err = bzzswap.NewSwap(self.swapParams, status.Swap, self) self.swap, err = bzzswap.NewSwap(self.swapParams, status.Swap, self.backend, self)
if err != nil { if err != nil {
return self.protoError(ErrSwap, "%v", err) return self.protoError(ErrSwap, "%v", err)
} }
@ -361,10 +369,9 @@ func (self *bzz) handleStatus() (err error) {
self.hive.addPeer(&peer{bzz: self}) self.hive.addPeer(&peer{bzz: self})
// hive sets syncstate so sync should start after node added // 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)
glog.V(logger.Info).Infof("[BZZ] syncronisation request sent with %v", self.syncState) self.syncRequest()
self.syncRequest()
}
return nil return nil
} }
@ -377,9 +384,12 @@ func (self *bzz) sync(state *syncState) error {
cnt := self.dbAccess.counter() cnt := self.dbAccess.counter()
remoteaddr := self.remoteAddr.Addr remoteaddr := self.remoteAddr.Addr
start, stop := self.hive.kad.KeyRange(remoteaddr) start, stop := self.hive.kad.KeyRange(remoteaddr)
// an explicitly received nil syncstate disables syncronisation
if state == nil { if state == nil {
state = newSyncState(start, stop, cnt) self.syncEnabled = false
glog.V(logger.Warn).Infof("[BZZ] peer %08x provided no sync state, setting up full sync: %v\n", remoteaddr[:4], state) glog.V(logger.Warn).Infof("[BZZ] syncronisation disabled for peer %v", self)
state = &syncState{DbSyncState: &storage.DbSyncState{}, Synced: true}
} else { } else {
state.synced = make(chan bool) state.synced = make(chan bool)
state.SessionAt = cnt state.SessionAt = cnt
@ -387,6 +397,7 @@ func (self *bzz) sync(state *syncState) error {
state.Start = storage.Key(start[:]) state.Start = storage.Key(start[:])
state.Stop = storage.Key(stop[:]) state.Stop = storage.Key(stop[:])
} }
glog.V(logger.Debug).Infof("[BZZ] syncronisation requested by peer %v at state %v", self, state)
} }
var err error var err error
self.syncer, err = newSyncer( self.syncer, err = newSyncer(
@ -394,11 +405,12 @@ func (self *bzz) sync(state *syncState) error {
storage.Key(remoteaddr[:]), storage.Key(remoteaddr[:]),
self.dbAccess, self.dbAccess,
self.unsyncedKeys, self.store, self.unsyncedKeys, self.store,
self.syncParams, state, self.syncParams, state, func() bool { return self.syncEnabled },
) )
if err != nil { if err != nil {
return self.protoError(ErrSync, "%v", err) return self.protoError(ErrSync, "%v", err)
} }
glog.V(logger.Detail).Infof("[BZZ] syncer set for peer %v", self)
return nil return nil
} }
@ -443,8 +455,13 @@ func (self *bzz) store(req *storeRequestMsgData) error {
} }
func (self *bzz) syncRequest() error { func (self *bzz) syncRequest() error {
req := &syncRequestMsgData{ req := &syncRequestMsgData{}
SyncState: self.syncState, if self.hive.syncEnabled {
glog.V(logger.Debug).Infof("[BZZ] syncronisation request to peer %v at state %v", self, self.syncState)
req.SyncState = self.syncState
}
if self.syncState == nil {
glog.V(logger.Warn).Infof("[BZZ] syncronisation disabled for peer %v at state %v", self, self.syncState)
} }
return self.send(syncRequestMsg, req) return self.send(syncRequestMsg, req)
} }
@ -496,7 +513,11 @@ func (self *bzz) protoErrorDisconnect(err *errs.Error) {
} }
func (self *bzz) send(msg uint64, data interface{}) error { func (self *bzz) send(msg uint64, data interface{}) error {
glog.V(logger.Debug).Infof("[BZZ] -> %v: %v (%T) to %v", msg, data, data, self) if self.hive.blockWrite {
return fmt.Errorf("network write blocked")
}
// self.messages = append(self.messages, "")
glog.V(logger.Detail).Infof("[BZZ] -> %v: %v (%T) to %v", msg, data, data, self)
err := p2p.Send(self.rw, msg, data) err := p2p.Send(self.rw, msg, data)
if err != nil { if err != nil {
self.Drop() self.Drop()

View file

@ -64,7 +64,7 @@ func newSyncDb(db *storage.LDBDatabase, key storage.Key, priority uint, bufferSi
batch: make(chan chan int), batch: make(chan chan int),
dbBatchSize: dbBatchSize, dbBatchSize: dbBatchSize,
} }
glog.V(logger.Debug).Infof("[BZZ] syncDb[peer: %v, priority: %v] - initialised", key.Log(), priority) glog.V(logger.Detail).Infof("[BZZ] syncDb[peer: %v, priority: %v] - initialised", key.Log(), priority)
// starts the main forever loop reading from buffer // starts the main forever loop reading from buffer
go syncdb.bufferRead(deliver) go syncdb.bufferRead(deliver)
@ -159,14 +159,13 @@ LOOP:
} }
self.dbTotal++ self.dbTotal++
self.total++ self.total++
// otherwise break after select // otherwise break after selec
case dbSize = <-self.batch: case dbSize = <-self.batch:
// explicit request for batch // explicit request for batch
if inBatch == 0 && quit != nil { if inBatch == 0 && quit != nil {
// there was no writes since the last batch so db depleted // there was no writes since the last batch so db depleted
// switch to buffer mode // switch to buffer mode
glog.V(logger.Detail).Infof("[BZZ] syncDb[%v] empty db: switching to buffer", self.priority) glog.V(logger.Debug).Infof("[BZZ] syncDb[%v] empty db: switching to buffer", self.priority)
db = nil db = nil
buffer = self.buffer buffer = self.buffer
dbSize <- 0 // indicates to 'caller' that batch has been written dbSize <- 0 // indicates to 'caller' that batch has been written
@ -175,9 +174,9 @@ LOOP:
} }
binary.BigEndian.PutUint64(counterValue, counter) binary.BigEndian.PutUint64(counterValue, counter)
batch.Put(self.counterKey, counterValue) batch.Put(self.counterKey, counterValue)
glog.V(logger.Detail).Infof("[BZZ] syncDb[%v] write batch %v/%v - %x - %x", self.priority, inBatch, counter, self.counterKey, counterValue) glog.V(logger.Debug).Infof("[BZZ] syncDb[%v] write batch %v/%v - %x - %x", self.priority, inBatch, counter, self.counterKey, counterValue)
batch = self.writeSyncBatch(batch) batch = self.writeSyncBatch(batch)
dbSize <- inBatch dbSize <- inBatch // indicates to 'caller' that batch has been written
inBatch = 0 inBatch = 0
continue LOOP continue LOOP
@ -222,7 +221,7 @@ LOOP:
func (self *syncDb) writeSyncBatch(batch *leveldb.Batch) *leveldb.Batch { func (self *syncDb) writeSyncBatch(batch *leveldb.Batch) *leveldb.Batch {
err := self.db.Write(batch) err := self.db.Write(batch)
if err != nil { if err != nil {
glog.V(logger.Detail).Infof("[BZZ] syncDb[%v] saving batch to db failed: %v", self.priority, err) glog.V(logger.Warn).Infof("[BZZ] syncDb[%v] saving batch to db failed: %v", self.priority, err)
return batch return batch
} }
return new(leveldb.Batch) return new(leveldb.Batch)
@ -275,7 +274,11 @@ func (self *syncDb) dbRead(useBatches bool, counter uint64, fun func(interface{}
// this could be called before all cnt items sent out // this could be called before all cnt items sent out
// so that loop is not blocking while delivering // so that loop is not blocking while delivering
// only relevant if cnt is large // only relevant if cnt is large
self.batch <- batchSizes select {
case self.batch <- batchSizes:
case <-self.quit:
return
}
// wait for the write to finish and get the item count in the next batch // wait for the write to finish and get the item count in the next batch
cnt = <-batchSizes cnt = <-batchSizes
batches++ batches++
@ -318,7 +321,7 @@ func (self *syncDb) dbRead(useBatches bool, counter uint64, fun func(interface{}
n++ n++
total++ total++
} }
glog.V(logger.Detail).Infof("[BZZ] syncDb[%v/%v] - db session closed, batches: %v, total: %v, session total from db: %v/%v", self.key.Log(), self.priority, batches, total, self.dbTotal, self.total) glog.V(logger.Debug).Infof("[BZZ] syncDb[%v/%v] - db session closed, batches: %v, total: %v, session total from db: %v/%v", self.key.Log(), self.priority, batches, total, self.dbTotal, self.total)
self.db.Write(del) // this could be async called only when db is idle self.db.Write(del) // this could be async called only when db is idle
it.Release() it.Release()
} }

View file

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"github.com/ethereum/go-ethereum/common/kademlia"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/logger/glog"
"github.com/ethereum/go-ethereum/swarm/storage" "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 // syncer is the agent that manages content distribution/storage replication/chunk storeRequest forwarding
type syncer struct { type syncer struct {
*SyncParams // sync parameters *SyncParams // sync parameters
syncF func() bool // if syncing is needed
key storage.Key // remote peers address key key storage.Key // remote peers address key
state *syncState // sync state for our dbStore state *syncState // sync state for our dbStore
syncStates chan *syncState // different stages of sync syncStates chan *syncState // different stages of sync
@ -165,6 +165,7 @@ func newSyncer(
store func(*storeRequestMsgData) error, store func(*storeRequestMsgData) error,
params *SyncParams, params *SyncParams,
state *syncState, state *syncState,
syncF func() bool,
) (*syncer, error) { ) (*syncer, error) {
syncBufferSize := params.SyncBufferSize syncBufferSize := params.SyncBufferSize
@ -172,6 +173,7 @@ func newSyncer(
dbBatchSize := params.RequestDbBatchSize dbBatchSize := params.RequestDbBatchSize
self := &syncer{ self := &syncer{
syncF: syncF,
key: remotekey, key: remotekey,
dbAccess: dbAccess, dbAccess: dbAccess,
syncStates: make(chan *syncState, 20), syncStates: make(chan *syncState, 20),
@ -191,35 +193,19 @@ func newSyncer(
// initialise a syncdb instance for each priority queue // initialise a syncdb instance for each priority queue
self.queues[i] = newSyncDb(db, remotekey, uint(i), syncBufferSize, dbBatchSize, self.deliver(uint(i))) 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) glog.V(logger.Info).Infof("[BZZ] syncer started: %v", state)
// launch chunk delivery service // launch chunk delivery service
go self.syncDeliveries() go self.syncDeliveries()
// launch sync task manager // launch sync task manager
go self.sync() if self.syncF() {
go self.sync()
}
// process unsynced keys to broadcast // process unsynced keys to broadcast
go self.syncUnsyncedKeys() go self.syncUnsyncedKeys()
return self, nil 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 // metadata serialisation
func encodeSync(state *syncState) (*json.RawMessage, error) { func encodeSync(state *syncState) (*json.RawMessage, error) {
data, err := json.MarshalIndent(state, "", " ") data, err := json.MarshalIndent(state, "", " ")
@ -238,7 +224,7 @@ func decodeSync(meta *json.RawMessage) (*syncState, error) {
if len(data) == 0 { if len(data) == 0 {
return nil, fmt.Errorf("unable to deserialise sync state from <nil>") return nil, fmt.Errorf("unable to deserialise sync state from <nil>")
} }
state := newSyncState(kademlia.Address{}, kademlia.Address{}, 0) state := &syncState{DbSyncState: &storage.DbSyncState{}}
err := json.Unmarshal(data, state) err := json.Unmarshal(data, state)
return state, err return state, err
} }
@ -330,7 +316,7 @@ func (self *syncer) syncState(state *syncState) {
// stop quits both request processor and saves the request cache to disk // stop quits both request processor and saves the request cache to disk
func (self *syncer) stop() { func (self *syncer) stop() {
close(self.quit) close(self.quit)
glog.V(logger.Debug).Infof("[BZZ] syncer[%v]: save sync request db backlog", self.key.Log()) glog.V(logger.Detail).Infof("[BZZ] syncer[%v]: stopand save sync request db backlog", self.key.Log())
for _, db := range self.queues { for _, db := range self.queues {
db.stop() db.stop()
} }
@ -478,6 +464,7 @@ LOOP:
glog.V(logger.Warn).Infof("[BZZ] syncer[%v]: unable to send unsynced keys: %v", err) glog.V(logger.Warn).Infof("[BZZ] syncer[%v]: unable to send unsynced keys: %v", err)
} }
unsynced = nil unsynced = nil
keys = nil
} }
// process item and add it to the batch // process item and add it to the batch
@ -503,11 +490,11 @@ LOOP:
deliveryRequest = nil deliveryRequest = nil
case <-newUnsyncedKeys: 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 // this 1 cap channel can wake up the loop
// signals that data is available to send if peer is ready to receive // signals that data is available to send if peer is ready to receive
newUnsyncedKeys = nil newUnsyncedKeys = nil
// this can only happen if keys is keys = self.keys[High]
case state, more = <-syncStates: case state, more = <-syncStates:
// this resets the state // this resets the state
@ -620,13 +607,16 @@ func (self *syncer) syncDeliveries() {
If sync mode is off then, requests are directly sent to deliveries If sync mode is off then, requests are directly sent to deliveries
*/ */
func (self *syncer) addRequest(req interface{}, ty int) { func (self *syncer) addRequest(req interface{}, ty int) {
// retrieve priority for request type // retrieve priority for request type name int8
priority := self.SyncPriorities[ty] priority := self.SyncPriorities[ty]
// sync mode for this type ON // sync mode for this type ON
if self.SyncModes[ty] { if self.syncF() || ty == DeliverReq {
self.addKey(req, priority, self.quit) if self.SyncModes[ty] {
} else { self.addKey(req, priority, self.quit)
self.addDelivery(req, priority, self.quit) } else {
self.addDelivery(req, priority, self.quit)
}
} }
} }

View file

@ -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)
}

View file

@ -1,3 +1,4 @@
//go:generate abigen --sol contract/chequebook.sol --pkg contract --out contract/jaak.go
package chequebook package chequebook
import ( import (
@ -11,14 +12,20 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "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/crypto"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "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 Chequebook package is a go API to the 'chequebook' ethereum smart contract
With convenience methods that allow using chequebook for 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 Backend is the interface for that
*/ */
const ( var (
gasToCash = "2000000" // gas cost of a cash transaction using chequebook gasToCash = big.NewInt(2000000) // gas cost of a cash transaction using chequebook
getSentAbiPre = "d75d691d" // sent amount accessor in the chequebook contract // gasToDeploy = big.NewInt(3000000)
cashAbiPre = "fbf788d6" // abi preamble signature for cash method of the chequebook
queryInterval = 15000000000 // 15 seconds
deployGas = "3000000"
// confirmationInterval = 3 * 10 * *11 // 5 minutes
) )
// 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 // rlp serialised cheque model for use with the chequebook
type Cheque struct { type Cheque struct {
// the address of the contract itself needed to avoid cross-contract submission // 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) 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 { 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) 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 // chequebook to create, sign cheques from single contract to multiple beneficiarys
// outgoing payment handler for peer to peer micropayments // outgoing payment handler for peer to peer micropayments
type Chequebook struct { type Chequebook struct {
path string // path to chequebook file path string // path to chequebook file
prvKey *ecdsa.PrivateKey // private key to sign cheque with prvKey *ecdsa.PrivateKey // private key to sign cheque with
lock sync.Mutex // lock sync.Mutex //
backend Backend // blockchain API backend bind.Backend // blockchain API
quit chan bool // when closed causes autodeposit to stop // abigen bind.ContractBackend // blockchain contract backend for abigen
owner common.Address // owner address (derived from pubkey) 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 // persisted fields
balance *big.Int // not synced with blockchain balance *big.Int // not synced with blockchain
contract common.Address // contract address contractAddr common.Address // contract address
sent map[common.Address]*big.Int //tallies for beneficiarys sent map[common.Address]*big.Int //tallies for beneficiarys
txhash string // tx hash of last deposit tx txhash string // tx hash of last deposit tx
threshold *big.Int // threshold that triggers autodeposit if not nil threshold *big.Int // threshold that triggers autodeposit if not nil
buffer *big.Int // buffer to keep on top of balance for fork protection 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) { func (self *Chequebook) String() string {
if (owner == common.Address{}) { return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", self.contractAddr.Hex(), self.owner.Hex(), self.balance, self.prvKey.PublicKey)
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 Validate(contract common.Address, backend Backend) (err error) { // NewChequebook(path, contract, prvKey, abibbackend, backend) creates a new Chequebook
if (contract == common.Address{}) { func NewChequebook(path string, contractAddr common.Address, prvKey *ecdsa.PrivateKey, backend bind.Backend) (self *Chequebook, err error) {
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) {
balance := new(big.Int) balance := new(big.Int)
sent := make(map[common.Address]*big.Int) sent := make(map[common.Address]*big.Int)
owner := crypto.PubkeyToAddress(prvKey.PublicKey)
self = &Chequebook{ chbook, err := contract.NewChequebook(contractAddr, backend)
balance: balance, if err != nil {
contract: contract, return nil, err
sent: sent,
path: path,
prvKey: prvKey,
backend: backend,
owner: owner,
} }
if (contract != common.Address{}) { transactOpts := bind.NewKeyedTransactor(prvKey)
glog.V(logger.Detail).Infof("[CHEQUEBOOK] new chequebook initialised from %v (owner: %v)", contract.Hex(), owner.Hex()) 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 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) // 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 var data []byte
data, err = ioutil.ReadFile(path) data, err = ioutil.ReadFile(path)
if err != nil { if err != nil {
@ -184,7 +149,11 @@ func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend) (sel
if err != nil { if err != nil {
return nil, err 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 return
} }
@ -207,7 +176,7 @@ func (self *Chequebook) UnmarshalJSON(data []byte) error {
if !ok { if !ok {
return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance) 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 { for addr, sent := range file.Sent {
self.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10) self.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10)
if !ok { if !ok {
@ -220,7 +189,7 @@ func (self *Chequebook) UnmarshalJSON(data []byte) error {
func (self *Chequebook) MarshalJSON() ([]byte, error) { func (self *Chequebook) MarshalJSON() ([]byte, error) {
var file = &chequebookFile{ var file = &chequebookFile{
Balance: self.balance.String(), Balance: self.balance.String(),
Contract: self.contract.Hex(), Contract: self.contractAddr.Hex(),
Owner: self.owner.Hex(), Owner: self.owner.Hex(),
Sent: make(map[string]string), Sent: make(map[string]string),
} }
@ -238,7 +207,7 @@ func (self *Chequebook) Save() (err error) {
if err != nil { if err != nil {
return err 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) 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) { func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch *Cheque, err error) {
defer self.lock.Unlock() defer self.lock.Unlock()
self.lock.Lock() self.lock.Lock()
glog.V(logger.Detail).Infof("[CHEQUEBOOK] prvKey: %v", self.prvKey)
if amount.Cmp(common.Big0) <= 0 { if amount.Cmp(common.Big0) <= 0 {
return nil, fmt.Errorf("amount must be greater than zero (%v)", amount) 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 := new(big.Int).Set(sent)
sum.Add(sum, amount) 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 { if err == nil {
ch = &Cheque{ ch = &Cheque{
Contract: self.contract, Contract: self.contractAddr,
Beneficiary: beneficiary, Beneficiary: beneficiary,
Amount: sum, Amount: sum,
Sig: sig, Sig: sig,
@ -301,7 +272,7 @@ func (self *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch *
// convenience method to cash any cheque // convenience method to cash any cheque
func (self *Chequebook) Cash(ch *Cheque) (txhash string, err error) { 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 // 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 // Backend() public accessor for backend
func (self *Chequebook) Backend() Backend { func (self *Chequebook) Backend() bind.Backend {
return self.backend return self.backend
} }
// Address() public accessor for contract // Address() public accessor for contract
func (self *Chequebook) Address() common.Address { func (self *Chequebook) Address() common.Address {
return self.contract return self.contractAddr
} }
// Deposit(amount) deposits amount to the chequebook account // 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 // deposit(amount) deposits amount to the chequebook account
// caller holds the lock // caller holds the lock
func (self *Chequebook) deposit(amount *big.Int) (string, error) { 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 // assume that transaction is actually successful, we add the amount to balance right away
if err != nil { 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 { } else {
self.balance.Add(self.balance, amount) 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 // 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()) 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 // inbox to deposit, verify and cash cheques
// from a single contract to single beneficiary // from a single contract to single beneficiary
// incoming payment handler for peer to peer micropayments // incoming payment handler for peer to peer micropayments
type Inbox struct { type Inbox struct {
lock sync.Mutex lock sync.Mutex
contract common.Address // peer's chequebook contract contract common.Address // peer's chequebook contract
beneficiary common.Address // local peer's receiving address beneficiary common.Address // local peer's receiving address
sender common.Address // local peer's address to send cashing tx from sender common.Address // local peer's address to send cashing tx from
signer *ecdsa.PublicKey // peer's public key signer *ecdsa.PublicKey // peer's public key
txhash string // tx hash of last cashing tx txhash string // tx hash of last cashing tx
backend Backend // blockchain API abigen bind.ContractBackend // blockchain API
quit chan bool // when closed causes autocash to stop session *contract.ChequebookSession // abi contract backend with tx opts
maxUncashed *big.Int // threshold that triggers autocashing quit chan bool // when closed causes autocash to stop
cashed *big.Int // cumulative amount cashed maxUncashed *big.Int // threshold that triggers autocashing
cheque *Cheque // last cheque, nil if none yet received cashed *big.Int // cumulative amount cashed
cheque *Cheque // last cheque, nil if none yet received
} }
// NewInbox(contract, beneficiary, signer, backend) constructor for Inbox // NewInbox(contract, beneficiary, signer, backend) constructor for Inbox
// not persisted, cumulative sum updated from blockchain when first cheque received // not persisted, cumulative sum updated from blockchain when first cheque received
// backend used to sync amount (Call) as well as cash the cheques (Transact) // 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 { if signer == nil {
return nil, fmt.Errorf("signer is null") 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{ self = &Inbox{
contract: contract, contract: contractAddr,
beneficiary: beneficiary, beneficiary: beneficiary,
sender: sender, sender: sender,
signer: signer, signer: signer,
backend: backend, session: session,
cashed: new(big.Int).Set(common.Big0), 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)) 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) { func (self *Inbox) Cash() (txhash string, err error) {
if self.cheque != nil { 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()) 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 self.cashed = self.cheque.Amount
} }
@ -548,7 +530,7 @@ func (self *Inbox) autoCash(cashInterval time.Duration) {
return 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) { func (self *Inbox) Receive(promise swap.Promise) (*big.Int, error) {
ch := promise.(*Cheque) ch := promise.(*Cheque)
@ -557,18 +539,12 @@ func (self *Inbox) Receive(promise swap.Promise) (*big.Int, error) {
var sum *big.Int var sum *big.Int
if self.cheque == nil { if self.cheque == nil {
// the sum is checked against the blockchain once a check is received // 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 { 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)
} }
sum = tally
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)
} else { } else {
sum = self.cheque.Amount sum = self.cheque.Amount
} }
@ -590,37 +566,6 @@ func (self *Inbox) Receive(promise swap.Promise) (*big.Int, error) {
return amount, err 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 // 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) { 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) 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 return amount, nil
} }
// Cash(backend) will cash the check using xeth backend to send a transaction // v/r/s representation of signature
// Beneficiary address should be unlocked func sig2vrs(sig []byte) (v *big.Int, r, s [32]byte) {
func (self *Cheque) Cash(sender common.Address, backend Backend) (string, error) { v = big.NewInt(int64(sig[64] + 27))
return backend.Transact(sender.Hex(), self.Contract.Hex(), "", "", "", gasToCash, self.cashAbiEncode()) 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
} }

View file

@ -1,88 +1,103 @@
package chequebook package chequebook
import ( import (
"crypto/ecdsa"
"math/big" "math/big"
"testing" "testing"
"time" "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/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"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 { type testBackend struct {
calls []string *backends.SimulatedBackend
errs []error }
txs []string
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 { func newTestBackend() *testBackend {
return &testBackend{} accs := accounts()
} return &testBackend{SimulatedBackend: backends.NewSimulatedBackend(accs...)}
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
} }
func (b *testBackend) GetTxReceipt(txhash common.Hash) *types.Receipt { func (b *testBackend) GetTxReceipt(txhash common.Hash) *types.Receipt {
return nil return nil
} }
func (b *testBackend) CodeAt(address string) string { func (b *testBackend) CodeAt(address common.Address) string {
return "" return ""
} }
func genAddr() common.Address { func (b *testBackend) BalanceAt(address common.Address) *big.Int {
prvKey, _ := crypto.GenerateKey() return big.NewInt(0)
return crypto.PubkeyToAddress(prvKey.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.DeployChequebook(deployTransactor, backend)
if err != nil {
return common.Address{}, err
}
backend.Commit()
return addr, nil
} }
func TestIssueAndReceive(t *testing.T) { func TestIssueAndReceive(t *testing.T) {
prvKey, _ := crypto.GenerateKey()
sender := genAddr()
path := "/tmp/checkbook.json" 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 { if err != nil {
t.Errorf("expected no error, got %v", err) t.Fatalf("deploy contract: expected no error, got %v", err)
} }
recipient := genAddr() chbook, err := NewChequebook(path, addr0, key0, backend)
chbook.sent[recipient] = new(big.Int).SetUint64(42) if err != nil {
t.Fatalf("expected no error, got %v", err)
}
chbook.sent[addr1] = new(big.Int).SetUint64(42)
amount := common.Big1 amount := common.Big1
ch, err := chbook.Issue(recipient, amount) ch, err := chbook.Issue(addr1, amount)
if err == nil { 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) chbook.balance = new(big.Int).Set(common.Big1)
if chbook.Balance().Cmp(common.Big1) != 0 { 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 { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
if chbook.Balance().Cmp(common.Big0) != 0 { if chbook.Balance().Cmp(common.Big0) != 0 {
t.Errorf("expected: %v, got %v", "0", chbook.Balance()) t.Errorf("expected: %v, got %v", "0", chbook.Balance())
} }
backend := newTestBackend() chbox, err := NewInbox(key1, addr0, addr1, &key0.PublicKey, backend)
backend.calls = []string{common.ToHex(big.NewInt(42).Bytes())}
backend.errs = []error{nil}
chbox, err := NewInbox(sender, recipient, recipient, &prvKey.PublicKey, backend)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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) t.Fatalf("expected no error, got %v", err)
} }
if received.Cmp(common.Big1) != 0 { if received.Cmp(big.NewInt(43)) != 0 {
t.Errorf("expected: %v, got %v", "1", received) t.Errorf("expected: %v, got %v", "43", received)
} }
} }
func TestCheckbookFile(t *testing.T) { func TestCheckbookFile(t *testing.T) {
prvKey, _ := crypto.GenerateKey()
sender := genAddr()
path := "/tmp/checkbook.json" path := "/tmp/checkbook.json"
chbook, err := NewChequebook(path, sender, prvKey, nil) backend := newTestBackend()
chbook, err := NewChequebook(path, addr0, key0, backend)
if err != nil { if err != nil {
t.Errorf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
recipient := genAddr() chbook.sent[addr1] = new(big.Int).SetUint64(42)
chbook.sent[recipient] = new(big.Int).SetUint64(42)
chbook.balance = new(big.Int).Set(common.Big1) chbook.balance = new(big.Int).Set(common.Big1)
chbook.Save() chbook.Save()
chbook, err = LoadChequebook(path, prvKey, nil) chbook, err = LoadChequebook(path, key0, backend, false)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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()) 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 { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
@ -135,31 +148,35 @@ func TestCheckbookFile(t *testing.T) {
} }
func TestVerifyErrors(t *testing.T) { func TestVerifyErrors(t *testing.T) {
prvKey, _ := crypto.GenerateKey()
sender0 := genAddr()
sender1 := genAddr()
path0 := "/tmp/checkbook0.json" path0 := "/tmp/checkbook0.json"
chbook0, err := NewChequebook(path0, sender0, prvKey, nil) backend := newTestBackend()
contr0, err := deploy(key0, common.Big2, backend.SimulatedBackend)
if err != nil { if err != nil {
t.Errorf("expected no error, got %v", err) 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" 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 { if err != nil {
t.Errorf("expected no error, got %v", err) 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) chbook0.balance = new(big.Int).Set(common.Big2)
chbook1.balance = new(big.Int).Set(common.Big1) chbook1.balance = new(big.Int).Set(common.Big1)
chbook0.sent[recipient0] = new(big.Int).SetUint64(42)
amount := common.Big1 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() time.Sleep(5)
backend.calls = []string{common.ToHex(big.NewInt(42).Bytes())} chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend)
backend.errs = []error{nil}
chbox, err := NewInbox(sender0, recipient0, recipient0, &prvKey.PublicKey, backend)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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) t.Fatalf("expected no error, got %v", err)
} }
if received.Cmp(common.Big1) != 0 { if received.Cmp(big.NewInt(43)) != 0 {
t.Errorf("expected: %v, got %v", "1", received) t.Errorf("expected: %v, got %v", "43", received)
} }
ch1, err := chbook0.Issue(recipient1, amount) ch1, err := chbook0.Issue(addr2, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
received, err = chbox.Receive(ch1) received, err = chbox.Receive(ch1)
t.Log(err) t.Logf("correct error: %v", err)
if err == nil { if err == nil {
t.Fatalf("expected receiver error, got none") t.Fatalf("expected receiver error, got none")
} }
ch2, err := chbook1.Issue(recipient0, amount) ch2, err := chbook1.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
received, err = chbox.Receive(ch2) received, err = chbox.Receive(ch2)
t.Log(err) t.Logf("correct error: %v", err)
if err == nil { if err == nil {
t.Fatalf("expected sender error, got none") t.Fatalf("expected sender error, got none")
} }
_, err = chbook1.Issue(recipient0, new(big.Int).SetInt64(-1)) _, err = chbook1.Issue(addr1, new(big.Int).SetInt64(-1))
t.Log(err) t.Logf("correct error: %v", err)
if err == nil { if err == nil {
t.Fatalf("expected incorrect amount error, got none") t.Fatalf("expected incorrect amount error, got none")
} }
received, err = chbox.Receive(ch0) received, err = chbox.Receive(ch0)
t.Log(err) t.Logf("correct error: %v", err)
if err == nil { if err == nil {
t.Fatalf("expected incorrect amount error, got none") t.Fatalf("expected incorrect amount error, got none")
} }
@ -209,31 +226,27 @@ func TestVerifyErrors(t *testing.T) {
} }
func TestDeposit(t *testing.T) { func TestDeposit(t *testing.T) {
prvKey, _ := crypto.GenerateKey() path0 := "/tmp/checkbook0.json"
sender := genAddr()
path := "/tmp/checkbook.json"
backend := newTestBackend() 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 { if err != nil {
t.Errorf("expected no error, got %v", err) t.Errorf("expected no error, got %v", err)
} }
balance := new(big.Int).SetUint64(42) balance := new(big.Int).SetUint64(42)
chbook.Deposit(balance) chbook.Deposit(balance)
if len(backend.txs) != 1 { backend.Commit()
t.Fatalf("expected 1 txs to send, got %v", len(backend.txs))
}
if chbook.balance.Cmp(balance) != 0 { if chbook.balance.Cmp(balance) != 0 {
t.Fatalf("expected balance %v, got %v", balance, chbook.balance) t.Fatalf("expected balance %v, got %v", balance, chbook.balance)
} }
recipient := genAddr()
amount := common.Big1 amount := common.Big1
_, err = chbook.Issue(recipient, amount) _, err = chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
backend.Commit()
exp := new(big.Int).SetUint64(41) exp := new(big.Int).SetUint64(41)
if chbook.balance.Cmp(exp) != 0 { if chbook.balance.Cmp(exp) != 0 {
t.Fatalf("expected balance %v, got %v", exp, chbook.balance) t.Fatalf("expected balance %v, got %v", exp, chbook.balance)
@ -241,36 +254,33 @@ func TestDeposit(t *testing.T) {
// autodeposit on each issue // autodeposit on each issue
chbook.AutoDeposit(0, balance, balance) chbook.AutoDeposit(0, balance, balance)
_, err = chbook.Issue(recipient, amount) _, err = chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
_, err = chbook.Issue(recipient, amount) backend.Commit()
_, err = chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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))
}
if chbook.balance.Cmp(balance) != 0 { if chbook.balance.Cmp(balance) != 0 {
t.Fatalf("expected balance %v, got %v", balance, chbook.balance) t.Fatalf("expected balance %v, got %v", balance, chbook.balance)
} }
// autodeposit off // autodeposit off
chbook.AutoDeposit(0, common.Big0, balance) chbook.AutoDeposit(0, common.Big0, balance)
_, err = chbook.Issue(recipient, amount) _, err = chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
_, err = chbook.Issue(recipient, amount) backend.Commit()
_, err = chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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) exp = new(big.Int).SetUint64(40)
if chbook.balance.Cmp(exp) != 0 { if chbook.balance.Cmp(exp) != 0 {
t.Fatalf("expected balance %v, got %v", exp, chbook.balance) 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 // autodeposit every 10ms if new cheque issued
interval := 30 * time.Millisecond interval := 30 * time.Millisecond
chbook.AutoDeposit(interval, common.Big1, balance) chbook.AutoDeposit(interval, common.Big1, balance)
_, err = chbook.Issue(recipient, amount) _, err = chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
_, err = chbook.Issue(recipient, amount) backend.Commit()
_, err = chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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) exp = new(big.Int).SetUint64(38)
if chbook.balance.Cmp(exp) != 0 { if chbook.balance.Cmp(exp) != 0 {
t.Fatalf("expected balance %v, got %v", exp, chbook.balance) t.Fatalf("expected balance %v, got %v", exp, chbook.balance)
} }
time.Sleep(3 * interval) time.Sleep(3 * interval)
if len(backend.txs) != 4 { backend.Commit()
t.Fatalf("expected 4 txs to send, got %v", len(backend.txs))
}
if chbook.balance.Cmp(balance) != 0 { if chbook.balance.Cmp(balance) != 0 {
t.Fatalf("expected balance %v, got %v", balance, chbook.balance) t.Fatalf("expected balance %v, got %v", balance, chbook.balance)
} }
exp = new(big.Int).SetUint64(40) exp = new(big.Int).SetUint64(40)
chbook.AutoDeposit(4*interval, exp, balance) chbook.AutoDeposit(4*interval, exp, balance)
_, err = chbook.Issue(recipient, amount) _, err = chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
_, err = chbook.Issue(recipient, amount) backend.Commit()
_, err = chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
time.Sleep(3 * interval) time.Sleep(3 * interval)
if len(backend.txs) != 4 { backend.Commit()
t.Fatalf("expected 4 txs to send, got %v", len(backend.txs))
}
if chbook.balance.Cmp(exp) != 0 { if chbook.balance.Cmp(exp) != 0 {
t.Fatalf("expected balance %v, got %v", exp, chbook.balance) t.Fatalf("expected balance %v, got %v", exp, chbook.balance)
} }
_, err = chbook.Issue(recipient, amount) _, err = chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
time.Sleep(1 * interval) 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 { if chbook.balance.Cmp(balance) != 0 {
t.Fatalf("expected balance %v, got %v", balance, chbook.balance) 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.AutoDeposit(1*interval, common.Big0, balance)
chbook.Stop() chbook.Stop()
_, err = chbook.Issue(recipient, common.Big1) _, err = chbook.Issue(addr1, common.Big1)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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 { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
time.Sleep(1 * interval) 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) exp = new(big.Int).SetUint64(39)
if chbook.balance.Cmp(exp) != 0 { if chbook.balance.Cmp(exp) != 0 {
t.Fatalf("expected balance %v, got %v", exp, chbook.balance) t.Fatalf("expected balance %v, got %v", exp, chbook.balance)
@ -362,26 +364,22 @@ func TestDeposit(t *testing.T) {
} }
func TestCash(t *testing.T) { func TestCash(t *testing.T) {
prvKey, _ := crypto.GenerateKey()
sender := genAddr()
path := "/tmp/checkbook.json" 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 { if err != nil {
t.Errorf("expected no error, got %v", err) t.Errorf("expected no error, got %v", err)
} }
recipient := genAddr() chbook.sent[addr1] = new(big.Int).SetUint64(42)
chbook.sent[recipient] = new(big.Int).SetUint64(42)
amount := common.Big1 amount := common.Big1
chbook.balance = new(big.Int).Set(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 { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
backend.Commit()
backend := newTestBackend() chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend)
backend.calls = []string{common.ToHex(big.NewInt(42).Bytes())}
backend.errs = []error{nil}
chbox, err := NewInbox(sender, recipient, recipient, &prvKey.PublicKey, backend)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
@ -391,20 +389,20 @@ func TestCash(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
_, err = ch.Cash(recipient, backend) _, err = ch.Cash(chbook.session)
if len(backend.txs) != 1 { backend.Commit()
t.Fatalf("expected 1 txs to send, got %v", len(backend.txs))
}
chbook.balance = new(big.Int).Set(common.Big3) chbook.balance = new(big.Int).Set(common.Big3)
ch0, err := chbook.Issue(recipient, amount) ch0, err := chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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 { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
backend.Commit()
interval := 10 * time.Millisecond interval := 10 * time.Millisecond
// setting autocash with interval of 10ms // setting autocash with interval of 10ms
@ -417,82 +415,119 @@ func TestCash(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
// after < interval time and 2 cheques received, no new cashing tx is sent backend.Commit()
if len(backend.txs) != 1 { // expBalance := big.NewInt(2)
t.Fatalf("expected 1 txs to send, got %v", len(backend.txs)) // 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 // after 3x interval time and 2 cheques received, exactly one cashing tx is sent
time.Sleep(4 * interval) time.Sleep(4 * interval)
if len(backend.txs) != 2 { backend.Commit()
t.Fatalf("expected 2 txs to send, got %v", len(backend.txs))
} // 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 // after stopping autocash no more tx are sent
ch2, err := chbook.Issue(recipient, amount) ch2, err := chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
chbox.Stop() chbox.Stop()
time.Sleep(interval) // make sure loop stops
_, err = chbox.Receive(ch2) _, err = chbox.Receive(ch2)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
time.Sleep(2 * interval) time.Sleep(2 * interval)
if len(backend.txs) != 2 { backend.Commit()
t.Fatalf("expected 2 txs to send, got %v", len(backend.txs)) // 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 // autocash below 1
chbook.balance = new(big.Int).Set(common.Big2) chbook.balance = big.NewInt(2)
chbox.AutoCash(0, common.Big1) chbox.AutoCash(0, common.Big1)
ch3, err := chbook.Issue(recipient, amount) ch3, err := chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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 { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
backend.Commit()
_, err = chbox.Receive(ch3) _, err = chbox.Receive(ch3)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
backend.Commit()
_, err = chbox.Receive(ch4) _, err = chbox.Receive(ch4)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
backend.Commit()
// 2 checks of amount 1 received, exactly 1 tx is sent // 2 checks of amount 1 received, exactly 1 tx is sent
if len(backend.txs) != 3 { // expBalance = big.NewInt(6)
t.Fatalf("expected 3 txs to send, got %v", len(backend.txs)) // 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 // autochash on receipt when maxUncashed is 0
chbook.balance = new(big.Int).Set(common.Big2) chbook.balance = new(big.Int).Set(common.Big2)
chbox.AutoCash(0, common.Big0) chbox.AutoCash(0, common.Big0)
ch5, err := chbook.Issue(recipient, amount) ch5, err := chbook.Issue(addr1, amount)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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 { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
_, err = chbox.Receive(ch5) _, err = chbox.Receive(ch5)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) 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) _, err = chbox.Receive(ch6)
if err != nil { if err != nil {
t.Fatalf("expected no error, got %v", err) t.Fatalf("expected no error, got %v", err)
} }
backend.Commit()
if len(backend.txs) != 5 { // expBalance = big.NewInt(6)
t.Fatalf("expected 5 txs to send, got %v", len(backend.txs)) // gotBalance = backend.BalanceAt(addr1)
} // if gotBalance.Cmp(expBalance) != 0 {
// t.Fatalf("expected beneficiary balance %v, got %v", expBalance, gotBalance)
// }
} }

View file

@ -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...)
}

View file

@ -4,19 +4,11 @@ import "mortal";
/// @author Daniel A. Nagy <daniel@ethdev.com> /// @author Daniel A. Nagy <daniel@ethdev.com>
contract chequebook is mortal { contract chequebook is mortal {
// Cumulative paid amount in wei to each beneficiary // Cumulative paid amount in wei to each beneficiary
mapping (address => uint256) sent; mapping (address => uint256) public sent;
/// @notice Overdraft event /// @notice Overdraft event
event Overdraft(address deadbeat); 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 /// @notice Cash cheque
/// ///
/// @param beneficiary beneficiary address /// @param beneficiary beneficiary address

View file

@ -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`

View file

@ -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...)
}

View file

@ -0,0 +1,19 @@
import "mortal";
/// @title Swarm Distributed Preimage Archive
/// @author Viktor Tron <viktor@ethdev.com>
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;
}
}
}

84
swarm/services/ens/ens.go Normal file
View file

@ -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
}

View file

@ -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)
}
}

View file

@ -9,12 +9,15 @@ import (
"sync" "sync"
"time" "time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/chequebook" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common/swap"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
"github.com/ethereum/go-ethereum/logger/glog" "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 // SwAP Swarm Accounting Protocol with
@ -31,14 +34,9 @@ var (
autoDepositBuffer = big.NewInt(100000000000000) // buffer that is surplus for fork protection etc (wei) 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) buyAt = big.NewInt(20000000000) // maximum chunk price host is willing to pay (wei)
sellAt = big.NewInt(20000000000) // minimum chunk price host requires (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) dropAt = 10000 // threshold that triggers disconnect (units)
maxRetries = 5
)
var (
retryInterval = 10 * time.Second
) )
type SwapParams struct { type SwapParams struct {
@ -52,15 +50,15 @@ type SwapProfile struct {
} }
type PayProfile struct { type PayProfile struct {
PublicKey string // check againsst signature of promise PublicKey string // check against signature of promise
Contract common.Address // address of chequebook contract Contract common.Address // address of chequebook contract
Beneficiary common.Address // recipient address for swarm sales revenue Beneficiary common.Address // recipient address for swarm sales revenue
privateKey *ecdsa.PrivateKey `json:"-"` privateKey *ecdsa.PrivateKey
publicKey *ecdsa.PublicKey `json:"-"` publicKey *ecdsa.PublicKey
owner common.Address owner common.Address
chbook *chequebook.Chequebook `json:"-"` chbook *chequebook.Chequebook
backend chequebook.Backend // backend bind.Backend
lock sync.RWMutex lock sync.RWMutex
} }
func DefaultSwapParams(contract common.Address, prvkey *ecdsa.PrivateKey) *SwapParams { func DefaultSwapParams(contract common.Address, prvkey *ecdsa.PrivateKey) *SwapParams {
@ -101,18 +99,18 @@ func DefaultSwapParams(contract common.Address, prvkey *ecdsa.PrivateKey) *SwapP
// n < 0 called when receiving chunks = receiving delivery responses // n < 0 called when receiving chunks = receiving delivery responses
// OR receiving cheques. // OR receiving cheques.
func NewSwap(local *SwapParams, remote *SwapProfile, proto swap.Protocol) (self *swap.Swap, err error) { func NewSwap(local *SwapParams, remote *SwapProfile, backend bind.Backend, proto swap.Protocol) (self *swap.Swap, err error) {
// check if remote chequebook is valid // check if remote chequebook is valid
// insolvent chequebooks suicide so will signal as invalid // insolvent chequebooks suicide so will signal as invalid
// TODO: monitoring a chequebooks events // TODO: monitoring a chequebooks events
var in *chequebook.Inbox var in *chequebook.Inbox
err = chequebook.Validate(remote.Contract, local.backend) err = bind.Validate(remote.Contract, contract.ContractDeployedCode, backend)
if err != nil { if err != nil {
glog.V(logger.Info).Infof("[BZZ] SWAP invalid contract %v for peer %v: %v)", remote.Contract.Hex()[:8], proto, err) glog.V(logger.Info).Infof("[BZZ] SWAP invalid contract %v for peer %v: %v)", remote.Contract.Hex()[:8], proto, err)
} else { } else {
// remote contract valid, create inbox // 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)), backend)
if err != nil { 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) 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 // cheque if local chequebook contract is valid
var out *chequebook.Outbox var out *chequebook.Outbox
err = chequebook.Validate(local.Contract, local.backend) err = bind.Validate(local.Contract, contract.ContractDeployedCode, backend)
if err != nil { 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) 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 { } else {
@ -161,35 +159,33 @@ func (self *SwapParams) Chequebook() *chequebook.Chequebook {
return self.chbook return self.chbook
} }
// func (self *SwapParams) Backend() bind.Backend {
// return self.backend
// }
func (self *SwapParams) PrivateKey() *ecdsa.PrivateKey { func (self *SwapParams) PrivateKey() *ecdsa.PrivateKey {
return self.privateKey return self.privateKey
} }
func (self *SwapParams) PublicKey() *ecdsa.PublicKey { // func (self *SwapParams) PublicKey() *ecdsa.PublicKey {
return self.publicKey // return self.publicKey
} // }
func (self *SwapParams) SetKey(prvkey *ecdsa.PrivateKey) { func (self *SwapParams) SetKey(prvkey *ecdsa.PrivateKey) {
self.privateKey = prvkey self.privateKey = prvkey
self.publicKey = &prvkey.PublicKey self.publicKey = &prvkey.PublicKey
} }
const (
confirmationInterval = 60000000000
timeout = 30000000000 // 30 sec
)
// setChequebook(path, backend) wraps the // setChequebook(path, backend) wraps the
// chequebook initialiser and sets up autoDeposit to cover spending. // 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() defer self.lock.Unlock()
self.lock.Lock() self.lock.Lock()
var valid bool var valid bool
done = make(chan bool) done = make(chan bool)
self.backend = backend err = bind.Validate(self.Contract, contract.ContractDeployedCode, backend)
err = chequebook.Validate(self.Contract, backend)
if err != nil { if err != nil {
owner := crypto.PubkeyToAddress(*(self.publicKey)) go self.deployChequebook(path, backend, done)
go self.deployChequebook(owner, path, done)
} else { } else {
valid = true valid = true
go func() { go func() {
@ -204,40 +200,39 @@ func (self *SwapParams) SetChequebook(path string, backend chequebook.Backend) (
return done, nil return done, nil
} }
func (self *SwapParams) deployChequebook(owner common.Address, path string, done chan bool) { func deploy(deployTransactor *bind.TransactOpts, backend bind.ContractBackend) (*types.Transaction, error) {
var timer = time.NewTimer(0).C _, tx, _, err := contract.DeployChequebook(deployTransactor, backend)
retries := 0 if err != nil {
var err error return nil, err
}
return tx, nil
}
// prvKey *ecdsa.PrivateKey, amount *big.Int,
func (self *SwapParams) deployChequebook(path string, backend bind.Backend, 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 var valid bool
OUT:
for { transactOpts := bind.NewKeyedTransactor(self.privateKey)
select { transactOpts.Value = self.AutoDepositBuffer
case <-timer: contract, err := bind.Deploy(deploy, contract.ContractDeployedCode, transactOpts, bind.DefaultDeployOptions(), backend)
// this is blocking if err != nil {
glog.V(logger.Info).Infof("[BZZ] SWAP Deploying new chequebook (owner: %v)", owner.Hex()) glog.V(logger.Error).Infof("[BZZ] SWAP unable to deploy new chequebook: %v", err)
var contract common.Address return
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) // need to save config at this point
if retries >= maxRetries { self.lock.Lock()
glog.V(logger.Info).Infof("[BZZ] SWAP unable to deploy new chequebook: giving up after %v retries", retries) self.Contract = contract
break OUT err = self.newChequebookFromContract(path, backend)
} self.lock.Unlock()
retries++ if err != nil {
timer = time.NewTicker(retryInterval).C glog.V(logger.Warn).Infof("[BZZ] SWAP error initialising cheque book (owner: %v): %v", owner.Hex(), err)
} else { } else {
// need to save config at this point valid = true
self.lock.Lock() glog.V(logger.Info).Infof("[BZZ] SWAP new chequebook deployed at %v (owner: %v)", contract.Hex(), owner.Hex())
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
}
}
} }
done <- valid done <- valid
close(done) close(done)
@ -245,8 +240,7 @@ OUT:
// initialise the chequebook from a persisted json file or create a new one // initialise the chequebook from a persisted json file or create a new one
// caller holds the lock // 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()) hexkey := common.Bytes2Hex(self.Contract.Bytes())
err := os.MkdirAll(filepath.Join(path, "chequebooks"), os.ModePerm) err := os.MkdirAll(filepath.Join(path, "chequebooks"), os.ModePerm)
if err != nil { if err != nil {
@ -254,7 +248,7 @@ func (self *SwapParams) newChequebookFromContract(path string, backend chequeboo
} }
chbookpath := filepath.Join(path, "chequebooks", hexkey+".json") 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 { if err != nil {
self.chbook, err = chequebook.NewChequebook(chbookpath, self.Contract, self.privateKey, backend) self.chbook, err = chequebook.NewChequebook(chbookpath, self.Contract, self.privateKey, backend)

View file

@ -152,13 +152,13 @@ func (self *Swap) Add(n int) error {
self.lock.Lock() self.lock.Lock()
self.balance += n self.balance += n
if !self.Sells && self.balance > 0 { 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() 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 { if !self.Buys && self.balance < 0 {
glog.V(logger.Detail).Infof("[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 (unable to buy)", 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) { 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) 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)

View file

@ -17,7 +17,7 @@ contract Swarm
} }
mapping (address => Bee) swarm; mapping (address => Bee) swarm;
// block number of transactions presenting chunks // block number of transactions presenting chunks
mapping (bytes32 => uint) presentedChunks; mapping (bytes32 => uint) presentedChunks;
@ -111,7 +111,7 @@ contract Swarm
b.reporter = msg.sender; b.reporter = msg.sender;
Report(signer); Report(signer);
} }
/// @notice Present a chunk in order to avoid losing deposit. /// @notice Present a chunk in order to avoid losing deposit.
/// ///
/// @param chunk chunk data /// @param chunk chunk data

View file

@ -97,7 +97,7 @@ func (self *TreeChunker) KeySize() int64 {
// String() for pretty printing // String() for pretty printing
func (self *Chunk) String() string { 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 // The treeChunkers own Hash hashes together

View file

@ -6,7 +6,6 @@ package storage
import ( import (
"fmt" "fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/compression/rle" "github.com/ethereum/go-ethereum/compression/rle"
"github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/iterator" "github.com/syndtr/goleveldb/leveldb/iterator"
@ -82,15 +81,3 @@ func (self *LDBDatabase) Close() {
// Close the leveldb database // Close the leveldb database
self.db.Close() 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)
}
}

View file

@ -2,12 +2,14 @@ package swarm
import ( import (
"bytes" "bytes"
"crypto/ecdsa"
"fmt" "fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common" "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/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/crypto"
"github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger"
@ -15,27 +17,62 @@ import (
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/swarm/api" "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/network"
"github.com/ethereum/go-ethereum/swarm/services/chequebook"
"github.com/ethereum/go-ethereum/swarm/services/ens"
"github.com/ethereum/go-ethereum/swarm/storage" "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 // the swarm stack
type Swarm struct { type Swarm struct {
config *api.Config // swarm configuration ethereum *eth.Ethereum
api *api.Api // high level api layer (fs/manifest) config *api.Config // swarm configuration
dbAccess *network.DbAccess // access to local chunk db iterator and storage counter api *api.Api // high level api layer (fs/manifest)
storage storage.ChunkStore // internal access to storage, common interface to cloud storage backends dns api.Resolver // DNS registrar
dpa *storage.DPA // distributed preimage archive, the local API to the storage with document level storage/retrieval support dbAccess *network.DbAccess // access to local chunk db iterator and storage counter
depo network.StorageHandler // remote request handler, interface between bzz protocol and the storage storage storage.ChunkStore // internal access to storage, common interface to cloud storage backends
cloud storage.CloudStore // procurement, cloud storage backend (can multi-cloud) dpa *storage.DPA // distributed preimage archive, the local API to the storage with document level storage/retrieval support
hive *network.Hive // the logistic manager depo network.StorageHandler // remote request handler, interface between bzz protocol and the storage
client *httpclient.HTTPClient // bzz capable light http client cloud storage.CloudStore // procurement, cloud storage backend (can multi-cloud)
hive *network.Hive // the logistic manager
client *httpclient.HTTPClient // bzz capable light http client
backend bind.Backend // simple blockchain Backend
privateKey *ecdsa.PrivateKey
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.backend,
PrvKey: self.privateKey,
}
}
type Backend interface {
GetTxReceipt(txhash common.Hash) *types.Receipt
BalanceAt(address common.Address) *big.Int
} }
// creates a new swarm service instance // creates a new swarm service instance
// implements node.Service // 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) { if bytes.Equal(common.FromHex(config.PublicKey), storage.ZeroKey) {
return nil, fmt.Errorf("empty public key") return nil, fmt.Errorf("empty public key")
@ -45,21 +82,25 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool)
} }
var ethereum *eth.Ethereum var ethereum *eth.Ethereum
if err := stack.Service(&ethereum); err != nil { if err := ctx.Service(&ethereum); err != nil {
return nil, fmt.Errorf("unable to find Ethereum service: %v", err) return nil, fmt.Errorf("unable to find Ethereum service: %v", err)
} }
self = &Swarm{ self = &Swarm{
config: config, config: config,
client: ethereum.HTTPClient(), ethereum: ethereum,
swapEnabled: swapEnabled,
client: ethereum.HTTPClient(),
privateKey: config.Swap.PrivateKey(),
} }
glog.V(logger.Debug).Infof("[BZZ] Setting up Swarm service components") glog.V(logger.Debug).Infof("[BZZ] Setting up Swarm service components")
// setup local store
hash := storage.MakeHashFunc(config.ChunkerParams.Hash) hash := storage.MakeHashFunc(config.ChunkerParams.Hash)
lstore, err := storage.NewLocalStore(hash, config.StoreParams) lstore, err := storage.NewLocalStore(hash, config.StoreParams)
if err != nil { if err != nil {
return return
} }
// setup local store
glog.V(logger.Debug).Infof("[BZZ] Set up local storage") glog.V(logger.Debug).Infof("[BZZ] Set up local storage")
self.dbAccess = network.NewDbAccess(lstore) self.dbAccess = network.NewDbAccess(lstore)
@ -69,6 +110,8 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool)
self.hive = network.NewHive( self.hive = network.NewHive(
common.HexToHash(self.config.BzzKey), // key to hive (kademlia base address) common.HexToHash(self.config.BzzKey), // key to hive (kademlia base address)
config.HiveParams, // configuration parameters config.HiveParams, // configuration parameters
swapEnabled, // SWAP enabled
syncEnabled, // syncronisation enabled
) )
glog.V(logger.Debug).Infof("[BZZ] Set up swarm network with Kademlia hive") glog.V(logger.Debug).Infof("[BZZ] Set up swarm network with Kademlia hive")
@ -78,9 +121,9 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool)
// setup cloud storage internal access layer // setup cloud storage internal access layer
self.storage = storage.NewNetStore(hash, lstore, cloud, config.StoreParams) 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) self.depo = network.NewDepo(hash, lstore, self.storage)
glog.V(logger.Debug).Infof("[BZZ] -> REmote Access to CHunks") glog.V(logger.Debug).Infof("[BZZ] -> REmote Access to CHunks")
@ -89,23 +132,22 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool)
glog.V(logger.Debug).Infof("[BZZ] -> Local Access to Swarm") glog.V(logger.Debug).Infof("[BZZ] -> Local Access to Swarm")
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage // Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
self.dpa = storage.NewDPA(dpaChunkStore, self.config.ChunkerParams) 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 // set up high level api
backend := api.NewEthApi(ethereum) transactOpts := bind.NewKeyedTransactor(self.privateKey)
backend.UpdateState() // backend := ethereum.ContractBackend()
self.api = api.NewApi(self.dpa, ethreg.New(backend), self.config) self.dns = ens.NewENS(transactOpts, ENSContractAddr, self.backend)
// Manifests for Smart Hosting glog.V(logger.Debug).Infof("[BZZ] -> Swarm Domain Name Registrar")
glog.V(logger.Debug).Infof("[BZZ] -> Level 2: Collection/Directory API")
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 return self, nil
} }
@ -129,6 +171,16 @@ func (self *Swarm) Start(net *p2p.Server) error {
net.AddPeer(node) net.AddPeer(node)
return nil 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") glog.V(logger.Warn).Infof("[BZZ] Starting Swarm service")
self.hive.Start( self.hive.Start(
@ -143,7 +195,7 @@ func (self *Swarm) Start(net *p2p.Server) error {
// start swarm http proxy server // start swarm http proxy server
if self.config.Port != "" { 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) glog.V(logger.Debug).Infof("[BZZ] Swarm http proxy started on port: %v", self.config.Port)
@ -154,7 +206,7 @@ func (self *Swarm) Start(net *p2p.Server) error {
"bzz": self.config.Port, "bzz": self.config.Port,
} }
for scheme, port := range schemes { 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) glog.V(logger.Debug).Infof("[BZZ] Swarm protocol handlers registered for url schemes: %v", schemes)
@ -175,27 +227,44 @@ func (self *Swarm) Stop() error {
// implements the node.Service interface // implements the node.Service interface
func (self *Swarm) Protocols() []p2p.Protocol { func (self *Swarm) Protocols() []p2p.Protocol {
proto, err := network.Bzz(self.depo, self.hive, self.dbAccess, self.config.Swap, self.config.SyncParams) proto, err := network.Bzz(self.depo, self.backend, self.hive, self.dbAccess, self.config.Swap, self.config.SyncParams)
if err != nil { if err != nil {
return nil return nil
} }
return []p2p.Protocol{proto} 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 { func (self *Swarm) Api() *api.Api {
return self.api return self.api
} }
// Backend interface implemented by eth or JSON-IPC client //
func (self *Swarm) SetChequebook(backend chequebook.Backend) (err error) { func (self *Swarm) SetChequebook() (err error) {
done, err := self.config.Swap.SetChequebook(self.config.Path, backend) done, err := self.config.Swap.SetChequebook(self.config.Path, self.backend)
if err != nil { if err != nil {
return err return err
} }
go func() { go func() {
ok := <-done ok := <-done
if ok { 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.config.Save()
self.hive.DropAll() self.hive.DropAll()
} }
@ -223,9 +292,19 @@ func NewLocalSwarm(datadir, port string) (self *Swarm, err error) {
} }
self = &Swarm{ self = &Swarm{
api: api.NewApi(dpa, nil, config), api: api.NewApi(dpa, nil),
config: config, config: config,
} }
return return
} }
// serialisable info about swarm
type Info struct {
*api.Config
*chequebook.Params
}
func (self *Info) Info() *Info {
return self
}

View file

@ -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

29
swarm/test/swap/00.sh Normal file
View file

@ -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

43
swarm/test/swap/01.sh Normal file
View file

@ -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

View file

@ -1,83 +1,55 @@
#!/bin/bash #!/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 dir=`dirname $0`
echo "Upload file '$1' to node $2 on port 85$2" 1>&2 source $dir/../../cmd/swarm/test.sh
key=`bash swarm/cmd/bzzup.sh $1 85$2`
echo -n $key
}
function down { #key port mkdir -p /tmp/swarm-test-files
echo "Download hash '$1' from node $2 on port 85$2" FILE_00=/tmp/swarm-test-files/00
wget -O- http://localhost:85$2/$1 > /dev/null && echo "got it" || echo "not found" 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 for f in $FILE_00 $FILE_01 $FILE_02 $FILE_03 $FILE_04; do
echo "Clean up for $1" randomfile 20 > $f
rm -rf ~/tmp/sync/$1/{bzz/*/chunks,bzz/*/requests/,bzz/*/bzz-peers.json,chaindata,nodes} done
} # options="--verbosity=0 --vmodule=swarm/network/*=6,common/chequebook/*=6,common/swap/*=6,common/kademlia/*=5"
function gethup { #index account key=/tmp/key
cp geth geth$1 swarm init 2 $options
echo "start node $1" swarm info 00
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 &" swarm info 01
./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 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 # exit 1;
killall -INT geth$1
}
wait=5 swarm up 00 $FILE_01|tail -n1 > $key
gethdown 00 swarm needs 00 $key $FILE_01
gethdown 01 swarm start 01 $options
swarm needs 01 $key $FILE_01
# ./geth --datadir ~/tmp/sync/00 --password <(echo bzz) account new swarm up 00 $FILE_02|tail -n1 > $key
BZZKEY00=5c6332e46a095feb9da1ed9803af2fa425f96aa6 swarm needs 00 $key $FILE_02
BZZKEY01=7dafa7436cba4b5b94a0452557b20b01d23bdc05 swarm needs 01 $key $FILE_02
echo "Fresh start, wipe datadir"
clean 00
clean 01
gethup 00 $BZZKEY00 & swarm up 01 $FILE_03|tail -n1 > $key
sleep 2 swarm needs 01 $key $FILE_03
echo "beyond\n" swarm needs 00 $key $FILE_03
key=$(up COPYING.LESSER 00)
echo "beyond\n"
down $key 00
echo "beyond\n" swarm stop 00
gethup 01 $BZZKEY01 & swarm up 01 $FILE_04|tail -n1 > $key
sleep $wait swarm needs 01 $key $FILE_04
down $key 01 swarm start 00 #--bzznoswap
gethdown 01 swarm needs 00 $key $FILE_04
swarm stop all
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

24
swarm/test/syncing/01.sh Normal file
View file

@ -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

30
swarm/test/syncing/02.sh Normal file
View file

@ -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