mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 06:06:44 +00:00
swap integration test
* add bzz.syncEnabled (hive.syncEnabled -> testapi/SyncEnabled | bzznosync param)) triggers nil sync State request to peer * syncEnabled false then historical items are not delivered * add swap initial scenarios, no funds, no retrieval; chequebook autodeployment; available funds -> retrieval * remove -ipcexp option from swarm script * tweaks to swarm/cmd files * add Balance to chequebook api * set chequebook balance from blockchain * fix chequebook tests after balance check * log balance when chequebook created * fix state setting in ethereum backend * add balanceAt to backend
This commit is contained in:
parent
863fbb4c37
commit
5714724bab
22 changed files with 360 additions and 163 deletions
|
|
@ -318,6 +318,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso
|
||||||
utils.WhisperEnabledFlag,
|
utils.WhisperEnabledFlag,
|
||||||
utils.SwarmConfigPathFlag,
|
utils.SwarmConfigPathFlag,
|
||||||
utils.SwarmSwapDisabled,
|
utils.SwarmSwapDisabled,
|
||||||
|
utils.SwarmSyncDisabled,
|
||||||
utils.SwarmPortFlag,
|
utils.SwarmPortFlag,
|
||||||
utils.SwarmAccountAddrFlag,
|
utils.SwarmAccountAddrFlag,
|
||||||
utils.ChequebookAddrFlag,
|
utils.ChequebookAddrFlag,
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,10 @@ var (
|
||||||
Name: "bzznoswap",
|
Name: "bzznoswap",
|
||||||
Usage: "Swarm SWAP disabled (false)",
|
Usage: "Swarm SWAP disabled (false)",
|
||||||
}
|
}
|
||||||
|
SwarmSyncDisabled = cli.BoolFlag{
|
||||||
|
Name: "bzznosync",
|
||||||
|
Usage: "Swarm Syncing 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",
|
||||||
|
|
@ -788,9 +792,10 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node.
|
||||||
if len(bzzport) > 0 {
|
if len(bzzport) > 0 {
|
||||||
bzzconfig.Port = bzzport
|
bzzconfig.Port = bzzport
|
||||||
}
|
}
|
||||||
swap := ctx.GlobalBool(SwarmSwapDisabled.Name)
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package chequebook
|
package chequebook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -8,22 +9,44 @@ import (
|
||||||
|
|
||||||
const Version = "1.0"
|
const Version = "1.0"
|
||||||
|
|
||||||
|
var errNoChequebook = errors.New("no chequebook")
|
||||||
|
|
||||||
type Api struct {
|
type Api struct {
|
||||||
ch *Chequebook
|
chequebookf func() *Chequebook
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApi(ch *Chequebook) *Api {
|
func NewApi(ch func() *Chequebook) *Api {
|
||||||
return &Api{ch}
|
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) {
|
func (self *Api) Issue(beneficiary common.Address, amount *big.Int) (cheque *Cheque, err error) {
|
||||||
return self.ch.Issue(beneficiary, amount)
|
ch := self.chequebookf()
|
||||||
|
if ch == nil {
|
||||||
|
return nil, errNoChequebook
|
||||||
|
}
|
||||||
|
return ch.Issue(beneficiary, amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Api) Cash(cheque *Cheque) (txhash string, err error) {
|
func (self *Api) Cash(cheque *Cheque) (txhash string, err error) {
|
||||||
return self.ch.Cash(cheque)
|
ch := self.chequebookf()
|
||||||
|
if ch == nil {
|
||||||
|
return "", errNoChequebook
|
||||||
|
}
|
||||||
|
return ch.Cash(cheque)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Api) Deposit(amount *big.Int) (txhash string, err error) {
|
func (self *Api) Deposit(amount *big.Int) (txhash string, err error) {
|
||||||
return self.ch.Deposit(amount)
|
ch := self.chequebookf()
|
||||||
|
if ch == nil {
|
||||||
|
return "", errNoChequebook
|
||||||
|
}
|
||||||
|
return ch.Deposit(amount)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ type Backend interface {
|
||||||
Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error)
|
Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, string, error)
|
||||||
GetTxReceipt(txhash common.Hash) *types.Receipt
|
GetTxReceipt(txhash common.Hash) *types.Receipt
|
||||||
CodeAt(address string) string
|
CodeAt(address string) string
|
||||||
|
BalanceAt(address common.Address) string
|
||||||
}
|
}
|
||||||
|
|
||||||
// rlp serialised cheque model for use with the chequebook
|
// rlp serialised cheque model for use with the chequebook
|
||||||
|
|
@ -165,13 +166,25 @@ func NewChequebook(path string, contract common.Address, prvKey *ecdsa.PrivateKe
|
||||||
owner: owner,
|
owner: owner,
|
||||||
}
|
}
|
||||||
if (contract != common.Address{}) {
|
if (contract != common.Address{}) {
|
||||||
glog.V(logger.Detail).Infof("[CHEQUEBOOK] new chequebook initialised from %v (owner: %v)", contract.Hex(), owner.Hex())
|
err = self.setBalanceFromBlockChain()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] new chequebook initialised from %v (owner: %v, balance: %s)", contract.Hex(), owner.Hex(), self.balance.String())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *Chequebook) setBalanceFromBlockChain() error {
|
||||||
|
balanceString := self.backend.BalanceAt(self.contract)
|
||||||
|
if _, ok := self.balance.SetString(balanceString, 10); !ok {
|
||||||
|
return fmt.Errorf("Incorrect balance: %s", balanceString)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 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 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 +197,14 @@ 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 {
|
||||||
|
err = self.setBalanceFromBlockChain()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.V(logger.Detail).Infof("[CHEQUEBOOK] loaded chequebook (%s, owner: %v, balance: %v) initialised from %v", self.contract.Hex(), self.owner.Hex(), self.balance, path)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,10 @@ func (b *testBackend) CodeAt(address string) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *testBackend) BalanceAt(address common.Address) string {
|
||||||
|
return "0"
|
||||||
|
}
|
||||||
|
|
||||||
func genAddr() common.Address {
|
func genAddr() common.Address {
|
||||||
prvKey, _ := crypto.GenerateKey()
|
prvKey, _ := crypto.GenerateKey()
|
||||||
return crypto.PubkeyToAddress(prvKey.PublicKey)
|
return crypto.PubkeyToAddress(prvKey.PublicKey)
|
||||||
|
|
@ -54,21 +58,21 @@ func TestIssueAndReceive(t *testing.T) {
|
||||||
prvKey, _ := crypto.GenerateKey()
|
prvKey, _ := crypto.GenerateKey()
|
||||||
sender := genAddr()
|
sender := genAddr()
|
||||||
path := "/tmp/checkbook.json"
|
path := "/tmp/checkbook.json"
|
||||||
chbook, err := NewChequebook(path, sender, prvKey, nil)
|
chbook, err := NewChequebook(path, sender, prvKey, newTestBackend())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v", err)
|
t.Fatalf("expected no error, got %v", err)
|
||||||
}
|
}
|
||||||
recipient := genAddr()
|
recipient := genAddr()
|
||||||
chbook.sent[recipient] = new(big.Int).SetUint64(42)
|
chbook.sent[recipient] = new(big.Int).SetUint64(42)
|
||||||
amount := common.Big1
|
amount := common.Big1
|
||||||
ch, err := chbook.Issue(recipient, amount)
|
ch, err := chbook.Issue(recipient, 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(recipient, amount)
|
||||||
|
|
@ -102,9 +106,9 @@ func TestCheckbookFile(t *testing.T) {
|
||||||
prvKey, _ := crypto.GenerateKey()
|
prvKey, _ := crypto.GenerateKey()
|
||||||
sender := genAddr()
|
sender := genAddr()
|
||||||
path := "/tmp/checkbook.json"
|
path := "/tmp/checkbook.json"
|
||||||
chbook, err := NewChequebook(path, sender, prvKey, nil)
|
chbook, err := NewChequebook(path, sender, prvKey, newTestBackend())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v", err)
|
t.Fatalf("expected no error, got %v", err)
|
||||||
}
|
}
|
||||||
recipient := genAddr()
|
recipient := genAddr()
|
||||||
chbook.sent[recipient] = new(big.Int).SetUint64(42)
|
chbook.sent[recipient] = new(big.Int).SetUint64(42)
|
||||||
|
|
@ -112,7 +116,7 @@ func TestCheckbookFile(t *testing.T) {
|
||||||
|
|
||||||
chbook.Save()
|
chbook.Save()
|
||||||
|
|
||||||
chbook, err = LoadChequebook(path, prvKey, nil)
|
chbook, err = LoadChequebook(path, prvKey, newTestBackend(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected no error, got %v", err)
|
t.Fatalf("expected no error, got %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -139,12 +143,12 @@ func TestVerifyErrors(t *testing.T) {
|
||||||
sender0 := genAddr()
|
sender0 := genAddr()
|
||||||
sender1 := genAddr()
|
sender1 := genAddr()
|
||||||
path0 := "/tmp/checkbook0.json"
|
path0 := "/tmp/checkbook0.json"
|
||||||
chbook0, err := NewChequebook(path0, sender0, prvKey, nil)
|
chbook0, err := NewChequebook(path0, sender0, prvKey, newTestBackend())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v", err)
|
t.Errorf("expected no error, got %v", err)
|
||||||
}
|
}
|
||||||
path1 := "/tmp/checkbook1.json"
|
path1 := "/tmp/checkbook1.json"
|
||||||
chbook1, err := NewChequebook(path1, sender1, prvKey, nil)
|
chbook1, err := NewChequebook(path1, sender1, prvKey, newTestBackend())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v", err)
|
t.Errorf("expected no error, got %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -179,7 +183,7 @@ func TestVerifyErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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")
|
||||||
}
|
}
|
||||||
|
|
@ -189,19 +193,19 @@ func TestVerifyErrors(t *testing.T) {
|
||||||
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(recipient0, 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")
|
||||||
}
|
}
|
||||||
|
|
@ -365,7 +369,7 @@ func TestCash(t *testing.T) {
|
||||||
prvKey, _ := crypto.GenerateKey()
|
prvKey, _ := crypto.GenerateKey()
|
||||||
sender := genAddr()
|
sender := genAddr()
|
||||||
path := "/tmp/checkbook.json"
|
path := "/tmp/checkbook.json"
|
||||||
chbook, err := NewChequebook(path, sender, prvKey, nil)
|
chbook, err := NewChequebook(path, sender, prvKey, newTestBackend())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error, got %v", err)
|
t.Errorf("expected no error, got %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -527,6 +527,18 @@ web3._extend({
|
||||||
params: 1,
|
params: 1,
|
||||||
inputFormatter: [null]
|
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({
|
new web3._extend.Method({
|
||||||
name: 'resolve',
|
name: 'resolve',
|
||||||
call: 'bzz_resolve',
|
call: 'bzz_resolve',
|
||||||
|
|
@ -580,12 +592,11 @@ web3._extend({
|
||||||
params: 1,
|
params: 1,
|
||||||
inputFormatter: [null]
|
inputFormatter: [null]
|
||||||
}),
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Property({
|
||||||
name: 'info',
|
name: 'balance',
|
||||||
call: 'chequebook_info',
|
getter: 'chequebook_balance',
|
||||||
params: 1,
|
outputFormatter: web3._extend.utils.toDecimal
|
||||||
inputFormatter: [null]
|
}),
|
||||||
}),
|
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'cash',
|
name: 'cash',
|
||||||
call: 'chequebook_cash',
|
call: 'chequebook_cash',
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ func NewEthApi(ethereum *eth.Ethereum) *ethApi {
|
||||||
// Note: this is not threadsafe, only called in JS single process and tests
|
// Note: this is not threadsafe, only called in JS single process and tests
|
||||||
func (self *ethApi) UpdateState() (wait chan *big.Int) {
|
func (self *ethApi) UpdateState() (wait chan *big.Int) {
|
||||||
wait = make(chan *big.Int)
|
wait = make(chan *big.Int)
|
||||||
self.state, _ = state.New(self.eth.BlockChain().GetBlockByNumber(0).Root(), self.eth.ChainDb())
|
self.state, _ = state.New(self.eth.BlockChain().CurrentBlock().Root(), self.eth.ChainDb())
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
eventSub := self.eth.EventMux().Subscribe(core.ChainHeadEvent{})
|
eventSub := self.eth.EventMux().Subscribe(core.ChainHeadEvent{})
|
||||||
|
|
@ -130,6 +130,10 @@ func (self *ethApi) StorageAt(addr, storageAddr string) string {
|
||||||
return self.state.GetState(common.HexToAddress(addr), common.HexToHash(storageAddr)).Hex()
|
return self.state.GetState(common.HexToAddress(addr), common.HexToHash(storageAddr)).Hex()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *ethApi) BalanceAt(address common.Address) string {
|
||||||
|
return self.state.GetBalance(address).String()
|
||||||
|
}
|
||||||
|
|
||||||
func (self *ethApi) CodeAt(address string) string {
|
func (self *ethApi) CodeAt(address string) string {
|
||||||
return common.ToHex(self.state.GetCode(common.HexToAddress(address)))
|
return common.ToHex(self.state.GetCode(common.HexToAddress(address)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,5 +16,13 @@ func NewControl(api *Api, hive *network.Hive) *Control {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Control) BlockNetworkRead(on bool) {
|
func (self *Control) BlockNetworkRead(on bool) {
|
||||||
self.hive.BlockNetworkRead(true)
|
self.hive.BlockNetworkRead(on)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Control) SyncEnabled(on bool) {
|
||||||
|
self.hive.SyncEnabled(on)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Control) SwapEnabled(on bool) {
|
||||||
|
self.hive.SwapEnabled(on)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ shift
|
||||||
# created to the latest, so that monitoring be easier with the same filename
|
# created to the latest, so that monitoring be easier with the same filename
|
||||||
# TODO: use this if GETH not set
|
# TODO: use this if GETH not set
|
||||||
# GETH=geth
|
# GETH=geth
|
||||||
|
echo "ls -l $GETH"
|
||||||
|
ls -l $GETH
|
||||||
|
|
||||||
# geth CLI params e.g., (dd=04, run=09)
|
# geth CLI params e.g., (dd=04, run=09)
|
||||||
datetag=`date "+%c%y%m%d-%H%M%S"|cut -d ' ' -f 5`
|
datetag=`date "+%c%y%m%d-%H%M%S"|cut -d ' ' -f 5`
|
||||||
|
|
@ -21,37 +23,46 @@ log=$root/log/$id.$datetag.log # /tmp/eth/04.09.log
|
||||||
linklog=$root/log/$id.current.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
|
stablelog=$root/log/$id.log # /tmp/eth/04.09.log
|
||||||
password=$id # 04
|
password=$id # 04
|
||||||
port=345$id # 34504
|
port=303$id # 34504
|
||||||
bzzport=622$id # 62204
|
bzzport=322$id # 32204
|
||||||
rpcport=62$id # 6204
|
rpcport=32$id # 3204
|
||||||
|
|
||||||
mkdir -p $root/data
|
mkdir -p $root/data
|
||||||
|
mkdir -p $root/enodes
|
||||||
|
mkdir -p $root/pids
|
||||||
mkdir -p $root/log
|
mkdir -p $root/log
|
||||||
ln -sf "$log" "$linklog"
|
ln -sf "$log" "$linklog"
|
||||||
# if we do not have an account, create one
|
# if we do not have an account, create one
|
||||||
# will not prompt for password, we use the double digit instance id as passwd
|
# 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
|
# NEVER EVER USE THESE ACCOUNTS FOR INTERACTING WITH A LIVE CHAIN
|
||||||
if [ ! -d "$root/keystore/$id" ]; then
|
keystoredir="$datadir/keystore/"
|
||||||
# echo "create an account with password $id"
|
echo "KeyStore dir: $keystoredir"
|
||||||
# echo "create an account with password $id [DO NOT EVER USE THIS ON LIVE]"
|
if [ ! -d "$keystoredir" ]; then
|
||||||
mkdir -p $root/keystore/$id
|
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
|
$GETH --datadir $datadir --password <(echo -n $id) account new >/dev/null 2>&1
|
||||||
# create account with password 00, 01, ...
|
# create account with password 00, 01, ...
|
||||||
# note that the account key will be stored also separately outside
|
# note that the account key will be stored also separately outside
|
||||||
# datadir
|
# datadir
|
||||||
# this way you can safely clear the data directory and still keep your key
|
# this way you can safely clear the data directory and still keep your key
|
||||||
# under `<rootdir>/keystore/dd
|
# under `<rootdir>/keystore/dd
|
||||||
|
# LS=`ls $datadir/keystore`
|
||||||
cp -R "$datadir/keystore" $root/keystore/$id
|
# 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
|
fi
|
||||||
|
|
||||||
# echo "copying keys $root/keystore/$id $datadir/keystore"
|
# # mkdir -p $datadir/keystore
|
||||||
# ls $root/keystore/$id/keystore/ $datadir/keystore
|
|
||||||
|
|
||||||
# mkdir -p $datadir/keystore
|
|
||||||
# if [ ! -d "$datadir/keystore" ]; then
|
# if [ ! -d "$datadir/keystore" ]; then
|
||||||
# echo "copying keys $root/keystore/$id $datadir/keystore"
|
# echo "copying keys $root/keystore/$id $datadir/keystore"
|
||||||
cp -R $root/keystore/$id/keystore/ $datadir/keystore/
|
# cp -R $root/keystore/$id/keystore/ $datadir/keystore/
|
||||||
# fi
|
# fi
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -63,10 +74,10 @@ else
|
||||||
pattern='\[\:\:\]'
|
pattern='\[\:\:\]'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# echo -n "enode for instance $id... "
|
geth="$GETH --datadir $datadir --port $port"
|
||||||
|
|
||||||
|
# echo -n "enode for instance $id... "
|
||||||
if [ ! "$GETH" = "" ] && [ ! -f $root/enodes/$id.enode ]; then
|
if [ ! "$GETH" = "" ] && [ ! -f $root/enodes/$id.enode ]; then
|
||||||
geth="$GETH --datadir $root/data/$id --port $port"
|
|
||||||
cmd="$geth js <(echo 'console.log(admin.nodeInfo.enode); exit();') "
|
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
|
# 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
|
eval $cmd 2>/dev/null |grep enode | perl -pe "s/$pattern/$ip_addr/g" | perl -pe "s/^/\"/; s/\s*\$/\"/;" > $root/enodes/$id.enode
|
||||||
|
|
@ -86,17 +97,17 @@ if [ ! -f $root/pids/$id.pid ]; then
|
||||||
# - listening on port 303dd, (like 30300, 30301, ...)
|
# - listening on port 303dd, (like 30300, 30301, ...)
|
||||||
# - with the account unlocked
|
# - with the account unlocked
|
||||||
# - launching json-rpc server on port 81dd (like 8100, 8101, 8102, ...)
|
# - launching json-rpc server on port 81dd (like 8100, 8101, 8102, ...)
|
||||||
BZZKEY=`$GETH --datadir=$datadir account list|head -n1|perl -ne '/([a-f0-9]{40})/ && print $1'`
|
# 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 -n "starting instance $id ($BZZKEY @ $datadir )..."
|
||||||
# echo "$GETH --datadir=$datadir \
|
echo "$geth \
|
||||||
# --identity=$id \
|
--identity=$id \
|
||||||
# --bzzaccount=$BZZKEY --bzzport=$bzzport \
|
--bzzaccount=$BZZKEY --bzzport=$bzzport \
|
||||||
# --port=$port \
|
--unlock=$BZZKEY \
|
||||||
# --unlock=$BZZKEY \
|
--password=<(echo -n $id) \
|
||||||
# --password=<(echo -n $id) \
|
--rpc --rpcport=$rpcport --rpccorsdomain='*' $* \
|
||||||
# --rpc --rpcport=$rpcport --rpccorsdomain='*' $* \
|
2>&1 | tee "$stablelog" > "$log" & # comment out if you pipe it to a tty etc.
|
||||||
# 2>&1 | tee "$stablelog" > "$log" & # comment out if you pipe it to a tty etc.
|
" >&2
|
||||||
# " >&2
|
|
||||||
|
|
||||||
if [ -f $log ] && [ -f $root/stablelog ]; then
|
if [ -f $log ] && [ -f $root/stablelog ]; then
|
||||||
cp $stablelog `cat $root/prevlog`
|
cp $stablelog `cat $root/prevlog`
|
||||||
|
|
@ -118,6 +129,7 @@ if [ ! -f $root/pids/$id.pid ]; then
|
||||||
# echo $pid > $root/pids/$id.pid
|
# echo $pid > $root/pids/$id.pid
|
||||||
#echo $! > $root/pids/$id.pid
|
#echo $! > $root/pids/$id.pid
|
||||||
while true; do
|
while true; do
|
||||||
|
# echo $GETH --ipcpath=$datadir/geth.ipc --exec="net" attach
|
||||||
$GETH --ipcpath=$datadir/geth.ipc --exec="net" attach > /dev/null 2>&1 && break
|
$GETH --ipcpath=$datadir/geth.ipc --exec="net" attach > /dev/null 2>&1 && break
|
||||||
sleep 1
|
sleep 1
|
||||||
echo -n "."
|
echo -n "."
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# !/bin/bash
|
# !/bin/bash
|
||||||
# bash intelligence <destination_app_json_path> <number_of_nodes> <name_prefix> <ws_server> <ws_secret>
|
# bash netstatconf.sh <number_of_nodes> <name_prefix> <ws_server> <ws_secret>
|
||||||
|
|
||||||
# sets up a eth-net-intelligence app.json for a local ethereum network cluster of nodes
|
# sets up a eth-net-intelligence app.json for a local ethereum network cluster of nodes
|
||||||
# - <number_of_clusters> is the number of clusters
|
# - <number_of_nodes> is the number of nodes in the cluster
|
||||||
# - <name_prefix> is a prefix for the node names as will appear in the listing
|
# - <name_prefix> is a prefix for the node names as will appear in the listing
|
||||||
# - <ws_server> is the eth-netstats server
|
# - <ws_server> is the eth-netstats server
|
||||||
# - <ws_secret> is the eth-netstats secret
|
# - <ws_secret> is the eth-netstats secret
|
||||||
|
|
|
||||||
|
|
@ -37,31 +37,8 @@ shift
|
||||||
# ip_addr=`curl ipecho.net/plain 2>/dev/null;echo `
|
# ip_addr=`curl ipecho.net/plain 2>/dev/null;echo `
|
||||||
|
|
||||||
# echo "external IP: $ip_addr"
|
# echo "external IP: $ip_addr"
|
||||||
swarmoptions='--vmdebug=false --maxpeers=20 --dev --shh=false --nodiscover --ipcexp'
|
swarmoptions='--vmdebug=false --maxpeers=20 --dev --shh=false --nodiscover'
|
||||||
|
tmpdir=/tmp
|
||||||
|
|
||||||
function needs {
|
|
||||||
id=$1
|
|
||||||
keyfile=$2
|
|
||||||
target=$3
|
|
||||||
dir=`dirname $3`
|
|
||||||
dest=$dir/down
|
|
||||||
mkdir -p $dest
|
|
||||||
file=$dest/`basename $target`
|
|
||||||
rm -f $file
|
|
||||||
echo -n "download to '$file'...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`
|
|
||||||
echo " => $key"
|
|
||||||
download $id $key $dest && cmp --silent $file $target && echo "PASS" || echo "FAIL"
|
|
||||||
# && ls -l $keyfile $file $target
|
|
||||||
}
|
|
||||||
|
|
||||||
function attach {
|
function attach {
|
||||||
id=$1
|
id=$1
|
||||||
|
|
@ -145,7 +122,6 @@ function init {
|
||||||
reset all
|
reset all
|
||||||
cluster $*
|
cluster $*
|
||||||
stop all
|
stop all
|
||||||
sleep 5
|
|
||||||
cluster $*
|
cluster $*
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,7 +139,7 @@ function reset {
|
||||||
function cluster {
|
function cluster {
|
||||||
N=$1
|
N=$1
|
||||||
shift
|
shift
|
||||||
# --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
|
vmodule='--vmodule=network/*=6,syncb=6,syncer=6,protocol=6,swap=6,chequebook=6'
|
||||||
echo "launching cluster of $N instances"
|
echo "launching cluster of $N instances"
|
||||||
# cmd="bash $srcdir/gethcluster.sh $root $network_id $N '' $swarmoptions $*"
|
# cmd="bash $srcdir/gethcluster.sh $root $network_id $N '' $swarmoptions $*"
|
||||||
# echo $cmd
|
# echo $cmd
|
||||||
|
|
@ -192,22 +168,46 @@ function cluster {
|
||||||
id=`printf "%02d" $i`
|
id=`printf "%02d" $i`
|
||||||
mkdir -p $dir/data/$id
|
mkdir -p $dir/data/$id
|
||||||
echo "launching node $i/$N ---> tail-f $dir/log/$id.log"
|
echo "launching node $i/$N ---> tail-f $dir/log/$id.log"
|
||||||
start $id $*
|
start $id $vmodule $*
|
||||||
done
|
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
|
function up { #port, file
|
||||||
echo "Upload file '$2' to node $1... " 1>&2
|
echo "Upload file '$2' to node $1... " 1>&2
|
||||||
file=`basename $2`
|
file=`basename $2`
|
||||||
key=`attach $1 "--exec 'bzz.upload(\"$2\", \"$file\")'"|tail -n1`
|
attach $1 "--exec 'bzz.upload(\"$2\", \"$file\")'"|tail -n1> /tmp/key
|
||||||
# key=`bash swarm/cmd/bzzup.sh $2 86$1`
|
# key=`bash swarm/cmd/bzzup.sh $2 86$1`
|
||||||
echo "-> $key"
|
cat /tmp/key
|
||||||
echo -n `eval echo $key`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function download {
|
function download {
|
||||||
echo "download to '$3'"
|
echo "download to '$3'"
|
||||||
|
echo attach $1 "--exec 'bzz.download(\"$2\", \"$3\")'"
|
||||||
attach $1 "--exec 'bzz.download(\"$2\", \"$3\")'" > /dev/null
|
attach $1 "--exec 'bzz.download(\"$2\", \"$3\")'" > /dev/null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,14 +234,22 @@ function clean { #index
|
||||||
}
|
}
|
||||||
|
|
||||||
function info {
|
function info {
|
||||||
|
echo "swarm node information"
|
||||||
echo "ROOTDIR: $root"
|
echo "ROOTDIR: $root"
|
||||||
echo "DATADIR: $root/data/$1"
|
echo "DATADIR: $root/$network_id/data/$1"
|
||||||
echo "LOGFILE: $root/log/$1.log"
|
echo "LOGFILE: $root/$network_id/log/$1.log"
|
||||||
|
echo "HTTPAPI: http://localhost:32$1"
|
||||||
|
echo "ETHPORT: 302$1"
|
||||||
|
echo "RPCPORT: 322$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}'`
|
||||||
}
|
}
|
||||||
|
|
||||||
case $cmd in
|
case $cmd in
|
||||||
"info" )
|
"info" )
|
||||||
info $*;;
|
info $*;;
|
||||||
|
"clean" )
|
||||||
|
clean $*;;
|
||||||
"needs" )
|
"needs" )
|
||||||
needs $*;;
|
needs $*;;
|
||||||
"up" )
|
"up" )
|
||||||
|
|
@ -264,4 +272,6 @@ case $cmd in
|
||||||
attach $*;;
|
attach $*;;
|
||||||
"log" )
|
"log" )
|
||||||
log $*;;
|
log $*;;
|
||||||
|
"less" )
|
||||||
|
less $*;;
|
||||||
esac
|
esac
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,12 @@ TEST_NAME=`basename $0 .sh`
|
||||||
|
|
||||||
export SWARM_BIN=$TEST_DIR/../../cmd/swarm
|
export SWARM_BIN=$TEST_DIR/../../cmd/swarm
|
||||||
export GETH=$SWARM_BIN/../../../geth
|
export GETH=$SWARM_BIN/../../../geth
|
||||||
export NETWORKID=622$TEST_NAME
|
export NETWORKID=322$TEST_NAME
|
||||||
export TMPDIR=/tmp/swarm-test
|
export TMPDIR=~/BZZ/swarm-test
|
||||||
export DATA_ROOT=$TMPDIR/$NETWORKID
|
export DATA_ROOT=$TMPDIR/$NETWORKID
|
||||||
# alias swarm='bash $SWARM_BIN/swarm.sh $DATA_ROOT $NETWORKID'
|
# alias swarm='bash $SWARM_BIN/swarm.sh $DATA_ROOT $NETWORKID'
|
||||||
EXTRA_ARGS=$*
|
EXTRA_ARGS=$*
|
||||||
|
|
||||||
rm -rf $DATA_ROOT
|
|
||||||
|
|
||||||
wait=1
|
wait=1
|
||||||
|
|
||||||
function swarm {
|
function swarm {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,25 +42,26 @@ 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 {
|
if err := p.swap.Add(-1); err == nil {
|
||||||
glog.V(logger.Warn).Infof("[BZZ] forwarder.Retrieve: unable to send retrieveRequest to peer [%v]: %v", chunk.Key.Log(), err)
|
p.retrieve(req)
|
||||||
}
|
break OUT
|
||||||
|
} else {
|
||||||
|
glog.V(logger.Warn).Infof("[BZZ] forwarder.Retrieve: unable to send retrieveRequest to peer [%v]: %v", chunk.Key.Log(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,12 @@ type Hive struct {
|
||||||
path string
|
path string
|
||||||
toggle chan bool
|
toggle chan bool
|
||||||
more chan bool
|
more chan bool
|
||||||
blockRead bool
|
|
||||||
blockWrite bool
|
// for testing only
|
||||||
|
swapEnabled bool
|
||||||
|
syncEnabled bool
|
||||||
|
blockRead bool
|
||||||
|
blockWrite bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -62,16 +66,26 @@ 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) {
|
func (self *Hive) BlockNetworkRead(on bool) {
|
||||||
self.blockRead = on
|
self.blockRead = on
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,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 serialised]}
|
||||||
from *peer //
|
from *peer //
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,7 +234,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 {
|
||||||
|
|
|
||||||
|
|
@ -78,11 +78,11 @@ type bzz struct {
|
||||||
|
|
||||||
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
|
||||||
|
|
@ -152,7 +152,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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,8 +273,6 @@ func (self *bzz) handle() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return self.protoError(ErrDecode, "->msg %v: %v", msg, err)
|
return self.protoError(ErrDecode, "->msg %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
|
||||||
|
|
@ -291,12 +289,14 @@ func (self *bzz) handle() error {
|
||||||
|
|
||||||
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, "->msg %v: %v", msg, err)
|
||||||
|
}
|
||||||
|
glog.V(logger.Debug).Infof("[BZZ] incoming payment: %s", req.String())
|
||||||
|
self.swap.Receive(int(req.Units), req.Promise)
|
||||||
}
|
}
|
||||||
glog.V(logger.Debug).Infof("[BZZ] incoming payment: %s", req.String())
|
|
||||||
self.swap.Receive(int(req.Units), req.Promise)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// no other message is allowed
|
// no other message is allowed
|
||||||
|
|
@ -366,10 +366,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -382,9 +381,10 @@ 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)
|
|
||||||
} else {
|
} else {
|
||||||
state.synced = make(chan bool)
|
state.synced = make(chan bool)
|
||||||
state.SessionAt = cnt
|
state.SessionAt = cnt
|
||||||
|
|
@ -399,7 +399,7 @@ 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)
|
||||||
|
|
@ -448,8 +448,9 @@ 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 {
|
||||||
|
req.SyncState = self.syncState
|
||||||
}
|
}
|
||||||
return self.send(syncRequestMsg, req)
|
return self.send(syncRequestMsg, req)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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{}
|
||||||
err := json.Unmarshal(data, state)
|
err := json.Unmarshal(data, state)
|
||||||
return state, err
|
return state, err
|
||||||
}
|
}
|
||||||
|
|
@ -625,10 +611,12 @@ func (self *syncer) addRequest(req interface{}, ty int) {
|
||||||
|
|
||||||
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -254,7 +254,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)
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ type Swarm struct {
|
||||||
|
|
||||||
// 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(stack *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")
|
||||||
|
|
@ -77,6 +77,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")
|
||||||
|
|
||||||
|
|
@ -110,12 +112,15 @@ func NewSwarm(stack *node.ServiceContext, config *api.Config, swapEnabled bool)
|
||||||
glog.V(logger.Debug).Infof("[BZZ] -> Web3 virtual server API")
|
glog.V(logger.Debug).Infof("[BZZ] -> Web3 virtual server API")
|
||||||
|
|
||||||
// set chequebook
|
// set chequebook
|
||||||
|
|
||||||
if swapEnabled {
|
if swapEnabled {
|
||||||
err = self.SetChequebook(backend)
|
err = self.SetChequebook(backend)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Unable to set chequebook for SWAP: %v", err)
|
return nil, fmt.Errorf("Unable to set chequebook for SWAP: %v", err)
|
||||||
}
|
}
|
||||||
glog.V(logger.Debug).Infof("[BZZ] -> cheque book for SWAP")
|
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")
|
||||||
}
|
}
|
||||||
return self, nil
|
return self, nil
|
||||||
}
|
}
|
||||||
|
|
@ -206,7 +211,7 @@ func (self *Swarm) APIs() []rpc.API {
|
||||||
rpc.API{Namespace, Version, api.NewControl(self.api, self.hive), false},
|
rpc.API{Namespace, Version, api.NewControl(self.api, self.hive), false},
|
||||||
// rpc.API{Namespace, Version, api.NewAdmin(self), false},
|
// rpc.API{Namespace, Version, api.NewAdmin(self), false},
|
||||||
// TODO: external apis exposed
|
// TODO: external apis exposed
|
||||||
rpc.API{"chequebook", chequebook.Version, chequebook.NewApi(self.config.Swap.Chequebook()), true},
|
rpc.API{"chequebook", chequebook.Version, chequebook.NewApi(self.config.Swap.Chequebook), true},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
29
swarm/test/swap/00.sh
Normal file
29
swarm/test/swap/00.sh
Normal 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
|
||||||
|
sleep $wait
|
||||||
|
swarm attach 00 -exec "'bzz.noSync(true)'"
|
||||||
|
swarm attach 01 -exec "'bzz.noSync(true)'"
|
||||||
|
swarm up 00 $FILE_00|tail -n1 > $key
|
||||||
|
swarm needs 00 $key $FILE_00
|
||||||
|
swarm needs 01 $key $FILE_00 | tail -1| grep -ql "PASS" && echo "FAIL" || echo "PASS <3"
|
||||||
|
|
||||||
|
FILE_01=/tmp/1K.1
|
||||||
|
randomfile 1 > $FILE_01
|
||||||
|
swarm up 01 $FILE_01|tail -1 > $key
|
||||||
|
swarm needs 01 $key $FILE_01
|
||||||
|
swarm needs 00 $key $FILE_01 | tail -1| grep -ql "PASS" && echo "FAIL" || echo "PASS <3"
|
||||||
|
|
||||||
|
swarm stop all
|
||||||
62
swarm/test/swap/01.sh
Normal file
62
swarm/test/swap/01.sh
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
echo "TEST swap/01:"
|
||||||
|
echo " two nodes that do not sync but have enough funds"
|
||||||
|
echo " after syncing, 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='miner/*=0,swarm/services/*=6,swarm/swarm=6,common/chequebook/*=6,swarm/network/depo=6,swarm/network/forwarding=6'"
|
||||||
|
# logargs='--verbosity=6'
|
||||||
|
|
||||||
|
|
||||||
|
# swarm init 2 --mine --bzznosync $logargs
|
||||||
|
# swarm stop all
|
||||||
|
|
||||||
|
swarm start 00 --mine --bzznosync $logargs
|
||||||
|
swarm start 01 --mine --bzznosync $logargs
|
||||||
|
|
||||||
|
# echo "Mining some ether..."
|
||||||
|
# sleep $mininginterval
|
||||||
|
|
||||||
|
swarm attach 00 -exec "'eth.getBalance(eth.accounts[0])'"
|
||||||
|
swarm attach 01 -exec "'eth.getBalance(eth.accounts[0])'"
|
||||||
|
swarm attach 00 -exec "'eth.getBalance(bzz.info().Swap.Contract)'"
|
||||||
|
swarm attach 01 -exec "'eth.getBalance(bzz.info().Swap.Contract)'"
|
||||||
|
swarm attach 00 -exec "'chequebook.balance'"
|
||||||
|
swarm attach 01 -exec "'chequebook.balance'"
|
||||||
|
|
||||||
|
randomfile 10 > $file
|
||||||
|
swarm up 00 $file|tail -n1 > $key
|
||||||
|
swarm needs 01 $key $file
|
||||||
|
swarm info 01
|
||||||
|
|
||||||
|
swarm attach 00 -exec "'eth.getBalance(eth.accounts[0])'"
|
||||||
|
swarm attach 01 -exec "'eth.getBalance(eth.accounts[0])'"
|
||||||
|
swarm attach 00 -exec "'eth.getBalance(bzz.info().Swap.Contract)'"
|
||||||
|
swarm attach 01 -exec "'eth.getBalance(bzz.info().Swap.Contract)'"
|
||||||
|
swarm attach 00 -exec "'chequebook.balance'"
|
||||||
|
swarm attach 01 -exec "'chequebook.balance'"
|
||||||
|
|
||||||
|
# randomfile 20 > $file
|
||||||
|
# swarm up 01 $file|tail -n1 > $key
|
||||||
|
# swarm needs 00 $key $file
|
||||||
|
|
||||||
|
# swarm attach 00 -exec "'eth.getBalance(eth.accounts[0])'"
|
||||||
|
# swarm attach 01 -exec "'eth.getBalance(eth.accounts[0])'"
|
||||||
|
# swarm attach 00 -exec "'eth.getBalance(bzz.info().Swap.Contract)'"
|
||||||
|
# swarm attach 01 -exec "'eth.getBalance(bzz.info().Swap.Contract)'"
|
||||||
|
# swarm attach 00 -exec "'chequebook.balance'"
|
||||||
|
# swarm attach 01 -exec "'chequebook.balance'"
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
# 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 stop all
|
||||||
Loading…
Reference in a new issue