Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Random transaction generator #171

Merged
merged 31 commits into from
Jun 20, 2018

Conversation

rawfalafel
Copy link
Contributor

@rawfalafel rawfalafel commented Jun 12, 2018

This PR, which resolves #153, is ready for review. See my comments below for more details.

👋 this PR isn't ready for review yet but I need help defining the requirements for this PR.

What kind of transactions should get sent through the feed? This branch just throws a random byte array into a types.Transaction object. Should the generator be smarter in order to target specific code paths on the other side of the event feed?

Also, should the generator be configurable? Would be nice if we could send transactions from geth console by having it read transactions from a JSON file. @rauljordan also mentioned making the intervals more random.

#125, #161, and possibly others could take advantage of a transaction generator but the requirements aren't clear to me, so I'm leaning towards keeping this simple and extensible for the next dev.

@prestonvanloon
Copy link
Member

The purpose of this feature is to give the proposers something to propose while we flush out p2p.
It shouldn't be fancy because it's going to be deleted after we add transaction subscription from p2p.

@rauljordan
Copy link
Contributor

Hi @rawfalafel the tx generator data isn't too important because for phase 1 of sharding, proposers will not care about the contents or "validity" of transactions and instead of just serialize them into blobs and package them into collation bodies.

If you are interested in extending past this simple PR into a more robust transaction generator, take a look at #17 which has been explored by @prestonvanloon a while back. The robust transaction generator would be more of a CLI tool or perhaps an additional service that can be registered to a sharding node that can be configurable and would be creating actual transactions from some sort of genesis state that we would configure in our own blockchain instance.

For this PR, however, feel free to keep it simple so we can get it merged soon. The item I outlined in the previous section is more of a bigger project that is outside of this scope.

@rawfalafel
Copy link
Contributor Author

rawfalafel commented Jun 13, 2018

Thanks for the clarification @prestonvanloon @rauljordan!

This PR is ready to review. The logic is incredibly simple; send a random transaction through the event feed every 5 seconds, and instruct the proposer to subscribe to the feed and propose a collation using that transaction. I tested the goroutine by spinning up my own node.

rand.Read(data)
txs = append(txs, types.NewTransaction(0, common.HexToAddress("0x0"),
nil, 0, nil, data))
for {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function body here needs test coverage but I didn't see any examples for testing side effects in the sharding package. Any ideas?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate more? What makes this difficult to test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked at every test in the sharding package but most of them assert the return value of a function. For this method I'd like to stub createCollation and check that it gets called when a transaction gets sent to the transaction feed but I don't see any mocking libraries being used in any of the test files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's still no test coverage for this PR. Open to ideas on how to add test coverage? Although having no test coverage (for now) is fine if you consider how much the behavior here is going to change in the near future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm not sure. We are facing the same issue in other places.

I suppose you could call p.cancel after a short timeout, but that feels like a hacky solution.

Since this is temporary code, I'm ok without testing this for loop, but this is a pattern we are already having issues within non-temporary code. I will spend some more time thinking about how to test this today.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait this is the non-temporary code.

@prestonvanloon prestonvanloon added this to the Ruby milestone Jun 13, 2018
txs = append(txs, types.NewTransaction(0, common.HexToAddress("0x0"),
nil, 0, nil, data))
for {
timeout := time.NewTimer(10 * time.Second)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use context for timeouts
https://golang.org/pkg/context/#WithTimeout

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay. Bear with me because I'm still new to Go. Are you saying call context.WithTimeout here and pass the new context down to createCollation, or to simply replace time.NewTimer with this context method and discard the context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like this:

for {
	ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
	defer cancel()

	select {
	case tx := <-requests:
		log.Info(fmt.Sprintf("Received transaction: %x", tx.Hash()))
		if err := p.createCollation(ctx, []*types.Transaction{tx}); err != nil {
			log.Error(fmt.Sprintf("Create collation failed: %v", err))
		}
	case <-ctx.Done():
		log.Error("Subscriber timed out")
	case err := <-p.txpoolSub.Err():
		log.Error(fmt.Sprintf("Subscriber failed: %v", err))
		cancel()
		return
	}
}

rand.Read(data)
txs = append(txs, types.NewTransaction(0, common.HexToAddress("0x0"),
nil, 0, nil, data))
for {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate more? What makes this difficult to test?

for range p.ticker.C {
tx := createTestTransaction()
nsent := p.transactionsFeed.Send(tx)
log.Info(fmt.Sprintf("Sent transaction %x to %d subscribers", tx.Hash(), nsent))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tx.Hash().Hex()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the %x formatter seems more logical to me, although I'd be splitting hairs.

"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/sharding"
)

// ShardTXPool handles a transaction pool for a sharded system.
type ShardTXPool struct {
p2p sharding.ShardP2P
p2p sharding.ShardP2P
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We decided on not using interfaces for things we most likely won't have a second implementation for. See #177 - just use *p2p.Server.

func (p *Proposer) proposeCollations() {
requests := make(chan *types.Transaction)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure you also defer close(requests)

@@ -23,6 +23,7 @@ type Proposer struct {
client *mainchain.SMCClient
shardp2p sharding.ShardP2P
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See 2 comments below about discontinuing interfaces for things with a single implementation. Just use *p2p.Server and *txpool.ShardTXPool - #177.

Copy link
Member

@terencechain terencechain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few feedbacks. Thanks for doing this @rawfalafel!

return nil
}

// proposeCollations listens to the transaction feed submits a collation when one is found
func (p *Proposer) proposeCollations() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is similar to simulateNotaryRequests() for Notary. Would it be better to place proposeCollations() under /proposer/utils.go? And maybe name it to simulateProposeCollation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not against it, but I guess this method doesn't know that the transactions it's being fed are simulated -- ShardTxPool does.

txs = append(txs, types.NewTransaction(0, common.HexToAddress("0x0"),
nil, 0, nil, data))
for {
timeout := time.NewTimer(10 * time.Second)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

select {
case tx := <-requests:
if err := p.createCollation([]*types.Transaction{tx}); err != nil {
log.Error(fmt.Sprintf("Create collation failed: %v", err))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be capitalizing first letter of error messages

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These error logs (not error objects) seem to be capitalized consistently throughout the entire package.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. I meant to not capitalize for nested error. But this is fine

if err := p.createCollation([]*types.Transaction{tx}); err != nil {
log.Error(fmt.Sprintf("Create collation failed: %v", err))
}
log.Info(fmt.Sprintf("Received transaction: %x", tx.Hash()))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this line should be before createCollation?

p2p sharding.ShardP2P
p2p sharding.ShardP2P
transactionsFeed *event.Feed
ticker *time.Ticker
}

// NewShardTXPool creates a new observer instance.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not right

return p.transactionsFeed
}

// sendTestTransaction sends a transaction with random bytes over a 5 second interval
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Period end function comments

@@ -15,6 +15,7 @@ import (
// and body. Header consists of shardID, ChunkRoot, period,
// proposer addr and signatures. Body contains serialized blob
// of a collations transactions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this newline

@rawfalafel
Copy link
Contributor Author

This PR is ready for another look!

p.txpoolSub = p.txpool.TransactionsFeed().Subscribe(requests)

for {
ctx, cancel := context.WithTimeout(p.ctx, 10*time.Second)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to define a new context here? Let's just use p.ctx.Done() in the select block. Also, this will cancel the goroutine right after the select block...we instead want this to run forever.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I derived a new context here to mimic the behavior here. If a single loop takes more than 10 seconds, whatever process is running gets canceled and the loop restarts. On the other hand if the parent context gets canceled we want the goroutine to be exit entirely.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me.

log.Error(fmt.Sprintf("Create collation failed: %v", err))
}
case <-ctx.Done():
log.Info("Context canceled")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a return statement after this line to stop the goroutine

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to return statement.
Remove the logging, it's not necessary (Or make it a debug level)

txs = append(txs, types.NewTransaction(0, common.HexToAddress("0x0"),
nil, 0, nil, data))
cancel()
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eliminate these two lines. Cancel will be called only if the service stops in the Stop() function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I called the derived context's cancel to let any goroutine that might have spawned within the for loop to abandon their work. The return doesn't belong here though.

@rawfalafel
Copy link
Contributor Author

I'm going to take another look at how contexts work tomorrow morning before making changes.

// Get current block number.
blockNumber, err := p.client.ChainReader().BlockByNumber(context.Background(), nil)
blockNumber, err := p.client.ChainReader().BlockByNumber(ctx, nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use p.ctx instead of passing ctx down?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that BlockByNumber could be given the opportunity to exit immediately if it hit ctx's 10 second timeout. But I removed the derived context now so this is passing down p.ctx again.

log.Error(fmt.Sprintf("Create collation failed: %v", err))
}
case <-ctx.Done():
log.Info("Context canceled")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log.Info can be more descriptive

@rawfalafel
Copy link
Contributor Author

I poked around at the behavior of the context derived by context.WithTimeout and decided to remove it because there were some unintended race conditions that caused createCollation to sometimes fail on startup. Since the timeout in the for loop isn't required behavior (as far as I know), it'd be simpler to just leave it out and add it back in if it's necessary.

This is ready for another look!

Yutaro Mori and others added 2 commits June 19, 2018 20:18
Sharding: proof of custody performance improvements
@rawfalafel
Copy link
Contributor Author

I pushed up a commit that adds tests to the proposer service by refactoring methods in the package. I found that because of the limitations to mocking smcClient, I passed in the mainchain interfaces to the goroutine function directly in order to test it. I also found that passing in the channels that the goroutine switched on made testing those cases easier as well.

Since adding tests led to unwanted scope creep, I'm down with reverting the last commit and merging this in without test coverage.

@rauljordan
Copy link
Contributor

Hey @rawfalafel let's revert and do the test coverage in a separate PR

@rawfalafel
Copy link
Contributor Author

reverted

Yutaro Mori added 2 commits June 20, 2018 10:38
…nctions more testable and add more unit tests"

This reverts commit e7608d4.
Copy link
Member

@terencechain terencechain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

)

func getPeriod(reader mainchain.Reader, periodLength *big.Int) (*big.Int, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we move it on the SMC client level so getPeriod can be used for notary as well? if not, please add a TODO to make it a good first issue

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pointing out, just in case, but this is from the reverted code

return new(big.Int).Div(blockNumber.Number(), periodLength), nil
}

func submitCollationHeader(txs []*types.Transaction, period *big.Int, shardID *big.Int, account *accounts.Account,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a quick function comment here for submitCollationHeader?

@terencechain terencechain merged commit 55e9cc4 into prysmaticlabs:master Jun 20, 2018
prestonvanloon pushed a commit that referenced this pull request Jul 22, 2018
sharding: random transaction generator
prestonvanloon pushed a commit that referenced this pull request Jul 22, 2018
sharding: random transaction generator
Former-commit-id: 69b7da1
prestonvanloon pushed a commit that referenced this pull request Jul 22, 2018
sharding: random transaction generator
Former-commit-id: b41891c474372ab54cbf1e3fa34dc7399f42c6d4 [formerly 69b7da1]
Former-commit-id: b642be5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement a Shard Transaction Generator
5 participants