Commit ffc450d5 authored by Zahoor Mohamed's avatar Zahoor Mohamed Committed by GitHub

fix forever loop timeout issue (#511)

* fix forever loop timeout issue
parent cc5412e0
......@@ -11,8 +11,9 @@ import (
"math/big"
"time"
"github.com/ethersphere/bee/pkg/swarm"
bmtlegacy "github.com/ethersphere/bmt/legacy"
"github.com/ethersphere/bee/pkg/swarm"
)
// Topic is an alias for a 32 byte fixed-size array which contains an encoding of a message topic
......@@ -41,7 +42,7 @@ const (
NonceSize = 32
LengthSize = 2
TopicSize = 32
MinerTimeout = 2 // seconds
MinerTimeout = 5 // seconds after which the mining will fail
)
// NewTopic creates a new Topic variable with the given input string
......@@ -163,32 +164,46 @@ func (m *Message) toChunk(targets Targets, span []byte) (swarm.Chunk, error) {
return nil, err
}
// hash chunk fields with different nonces until an acceptable one is found
for start := time.Now(); ; {
s := append(append(span, nonce...), b...) // serialize chunk fields
hash1, err := hashBytes(s)
errC := make(chan error)
var hash, s []byte
go func() {
defer close(errC)
// mining operation: hash chunk fields with different nonces until an acceptable one is found
for {
s = append(append(span, nonce...), b...) // serialize chunk fields
hash, err = hashBytes(s)
if err != nil {
return nil, err
errC <- err
return
}
// take as much of the hash as the targets are long
if contains(targets, hash1[:targetsLen]) {
if contains(targets, hash[:targetsLen]) {
// if nonce found, stop loop and return chunk
return swarm.NewChunk(swarm.NewAddress(hash1), s), nil
errC <- nil
return
}
// else, add 1 to nonce and try again
nonceInt.Add(nonceInt, big.NewInt(1))
// loop around in case of overflow after 256 bits
if nonceInt.BitLen() > (NonceSize * swarm.SpanSize) {
// Test if timeout after after every 256 iteration
if time.Since(start) > (MinerTimeout * time.Second) {
break
}
nonceInt = big.NewInt(0)
}
nonce = padBytesLeft(nonceInt.Bytes()) // pad in case Bytes call is not 32 bytes long
}
}()
// checks whether the mining is completed or times out
select {
case err := <-errC:
if err == nil {
return swarm.NewChunk(swarm.NewAddress(hash), s), nil
}
return nil, err
case <-time.After(MinerTimeout * time.Second):
return nil, ErrMinerTimeout
}
}
// hashBytes hashes the given serialization of chunk fields with the hashing func
......
......@@ -6,6 +6,7 @@ package trojan_test
import (
"bytes"
"crypto/rand"
"encoding/binary"
"reflect"
"testing"
......@@ -16,11 +17,11 @@ import (
)
// arbitrary targets for tests
var t1 = trojan.Target([]byte{57, 120})
var t2 = trojan.Target([]byte{209, 156})
var t3 = trojan.Target([]byte{156, 38})
var t4 = trojan.Target([]byte{89, 19})
var t5 = trojan.Target([]byte{22, 129})
var t1 = trojan.Target([]byte{57})
var t2 = trojan.Target([]byte{209})
var t3 = trojan.Target([]byte{156})
var t4 = trojan.Target([]byte{89})
var t5 = trojan.Target([]byte{22})
var testTargets = trojan.Targets([]trojan.Target{t1, t2, t3, t4, t5})
// arbitrary topic for tests
......@@ -124,6 +125,24 @@ func TestWrapFail(t *testing.T) {
}
}
// TestWrapTimeout tests for mining timeout and avoid forever loop
func TestWrapTimeout(t *testing.T) {
m := newTestMessage(t)
// a large target will take more than MinerTimeout seconds, so timeout error will be triggered
buf := make([]byte, swarm.SectionSize)
_, err := rand.Read(buf)
if err != nil {
t.Fatal(err)
}
target := trojan.Target(buf)
targets := trojan.Targets([]trojan.Target{target})
if _, err := m.Wrap(targets); err != trojan.ErrMinerTimeout {
t.Fatalf("expected error when having lengthy target to be %q, but got %v", trojan.ErrMinerTimeout, err)
}
}
// TestPadBytes tests that different types of byte slices are correctly padded with leading 0s
// all slices are interpreted as big-endian
func TestPadBytes(t *testing.T) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment