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

Transaction parsing #5

Closed
wants to merge 5 commits into from
Closed

Transaction parsing #5

wants to merge 5 commits into from

Conversation

christosporios
Copy link
Member

@christosporios christosporios commented May 16, 2017

For every TX, checks whether the transaction is a TIR transaction, parses it into a TrustChange as discussed in the spec document and applies it to the trust graph.

Unit tests for the public methods as well as an E2E test that verifies that the TIR full node is processing all transactions are included. Some tests for complicated TX cases are missing and are TODO.

@christosporios christosporios self-assigned this May 16, 2017
@christosporios christosporios changed the title Parse trust-increasing transactions Transaction parsing May 19, 2017
@dionyziz
Copy link
Member

Squash your refactoring changes into atomic commits: Each PR should have many commits like you have, but if a future commit undoes something of a previous commit, the commits should be squashed. If you have big commits that do many things, they should be split to several commits where each does one thing (but the code should work after every commit).

@dionyziz
Copy link
Member

Rewrite your commit messages based on Chris Beam's recommendations. In particular, do not end your commits in "." and limit the length of your commit messages so that they don't wrap around (you can use the extensive description for that)

class FullNode extends bcoin.fullnode {


constructor(options ) {
Copy link
Member

Choose a reason for hiding this comment

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

What's going on with the whitespace overall?

Copy link
Member Author

Choose a reason for hiding this comment

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

The flow-type annotated source code in src/ is compiled to lib/ by flow-remove-types, which replaces flow syntax with whitespace to make the code runnable.

Do not review the code in lib.

Copy link
Member

Choose a reason for hiding this comment

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

Don't check in compiled code. Instead, provide the script to compile it as part of your package.json.

Copy link
Member Author

Choose a reason for hiding this comment

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

The build script is already in package.json.

It's common to check in the compiled source code in compiled javascript libraries. I assume the reason is that a compiled javascript program is still cross-platform (unlike a binary), and that it's a bit more convenient to not have to install build tools (in this case Flow and flow-remove-types) when trying to run an older version of the library. On the other hand it does take up some space and looks ugly. I think it would be ideal if there were a way for git to treat the lib/ folder like it treats binaries (e.g. calculate no diffs).

@@ -0,0 +1,14 @@
//
var bcoin = require('bcoin');
var TrustIsRisk = require('./trust_is_risk');
Copy link
Member

Choose a reason for hiding this comment

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

Popular JS libraries use - in their names. Let's use "trust-is-risk" for the npm package name? This would imply a similar name here.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the file name, not the npm package name.


constructor(options ) {
super(options);
this.trust = new TrustIsRisk(this);
Copy link
Member

Choose a reason for hiding this comment

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

Trust is risk requires a full node instance? Can you discuss this architectural decision? It seems that the TIR library may need less than that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Possibly, however I thought we agreed that the initial version will be bitcoin full node. This is in the spec and was discussed in the review meeting.

Please note that you don't have to use the full node class provided here, you can just use the TIR class alone and connect it to any other type of node manually. We can provide a spv_node implementation after the initial version is working.


getDirectTrust(from , to ) {
if (!this.directTrust.hasOwnProperty(from)) return 0;
if (!this.directTrust[from].hasOwnProperty(to)) return 0;
Copy link
Member

@dionyziz dionyziz May 24, 2017

Choose a reason for hiding this comment

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

Use in everywhere

Copy link
Member Author

Choose a reason for hiding this comment

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

I think it's clearer to use hasOwnProperty here, because I only want to check if the object has the property set on itself, not in its prototype chain.

Practically however and in this specific case this would be the same, so I don't feel very strongly about this.


var trustChanges = this.getTrustChanges(tx);
if (trustChanges.length === 0) return false;
else {
Copy link
Member

@dionyziz dionyziz May 24, 2017

Choose a reason for hiding this comment

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

Avoid else-after-return, especially in initial error-checking cases like this. Maybe run jslint and enable the rules you'd like.

Copy link
Member Author

Choose a reason for hiding this comment

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

I disagree with this: I use else after return only sometimes, when it enhances readability. This is usually when I want to distinguish between two cases, none of which are erroneous. I don't use else after return in initial error checking cases.

In this particular case, I'm using else exactly because an empty trust changes list is not error, but one of the two things that can happen: either this tx is a TIR transaction, or it's not.

}

getDirectTrust(from , to ) {
if (!this.directTrust.hasOwnProperty(from)) return 0;
Copy link
Member

@dionyziz dionyziz May 24, 2017

Choose a reason for hiding this comment

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

Always use { } even if your body is one line.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure if the risk outweights the readability gain. The risk of accidentally causing unintentional behavior that isn't very easily caught in tests and code reviews is very low. Also one-line if statements are allowed in many style rules (e.g. google style guide), and bcoin uses them as well.

@@ -0,0 +1,202 @@
// @flow
Copy link
Member

Choose a reason for hiding this comment

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

Why is this source code checked in twice?

Copy link
Member Author

Choose a reason for hiding this comment

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

See comment above.

constructor(node ) {
this.node = node;
this.directTrust = {};
this.TXToTrust = {};
Copy link
Member

Choose a reason for hiding this comment

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

Comments for what these hold.

Copy link
Member Author

Choose a reason for hiding this comment

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

There's type declarations in the source file under src/, which describe what types these will hold. If you still think that something is unclear and requires a comment, please comment on the specific member variable.

package.json Outdated
"should": "^11.2.1"
"should": "^11.2.1",
"should-sinon": "0.0.5",
"sinon": "^2.2.0"
Copy link
Member

Choose a reason for hiding this comment

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

+mocha

@@ -5,7 +5,7 @@
"main": "lib/index.js",
"scripts": {
"build": "flow-remove-types -d lib/ src/",
"test": "npm run build && mocha",
"test": "npm run build && mocha -t 5000",
Copy link
Member

Choose a reason for hiding this comment

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

This seems bad, can you debug why the testcases are running so long by building a minimal testcase?

Copy link
Member Author

Choose a reason for hiding this comment

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

The E2E regtest test takes a while to run in particular (~1.5s on my machine), so I'm setting the time limit to something higher to avoid flakiness on slower machines.

As we discussed in a meeting, this is because apparently I have to wait (sleep) until some bcoin event handlers fire. I haven't been able to find a better solution, and I think overall it's better to have the tests than remove them because they take a bit longer to run. We can discuss this again tonight.

+ `0x${address.hash.toString('hex')} OP_EQUALVERIFY OP_CHECKSIG`);

return new bcoin.primitives.Output({script, value});
}
Copy link
Member

Choose a reason for hiding this comment

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

I'm surprised this helper isn't available as part of the standard bcoin js library. Perhaps we should patch it instead? It seems like it could belong there.

Copy link
Member Author

Choose a reason for hiding this comment

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

There is this: https://github.com/bcoin-org/bcoin/blob/master/lib/script/script.js#L1677 which I probably should have used to create the script, and then you can create an output from the script.

There doesn't seem to be a way to create a P2PKH output directly.


it('should call trust.addTX() on every transaction', async function() {
var sender = await testHelpers.getWallet(walletDB, 'sender');
var receiver = await testHelpers.getWallet(walletDB, 'receiver');
Copy link
Member

Choose a reason for hiding this comment

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

2nd parameter seems unused?

Copy link
Member Author

Choose a reason for hiding this comment

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

That parameter is the ID parameter of the wallet and needs to be unique. Here I'm setting it to something descriptive.

// Make the coin spendable.
consensus.COINBASE_MATURITY = 0;

await testHelpers.time(100);
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need this and the next timeout? Why isn't it a timeout of 0?

Copy link
Member Author

@christosporios christosporios May 24, 2017

Choose a reason for hiding this comment

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

We need this timeout because even though the node has been mined at this point, the next call will fail with "not enough balance" if we don't wait long enough.

We need the next timeout because the 'tx' event doesn't fire until after a few milliseconds on the bcoin.fullnode object.

Sleeping for 0 milliseconds would run all the events that are currently in the event queue and return to the test right after. This is not sufficient however, as the event handlers for those events may emit more events that will be added to the end of the event queue, and therefore processed after the test finishes.

should(tir.getDirectTrust(alice, bob)).equal(0);
should(tir.getDirectTrust(bob, alice)).equal(0);
should(tir.getDirectTrust("1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND", alice)).equal(0);
should(tir.getDirectTrust(alice, "1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND")).equal(0);
Copy link
Member

@dionyziz dionyziz May 24, 2017

Choose a reason for hiding this comment

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

Use charlie instead of this string, since you already defined him as a constant

it('returns zero for two arbitary parties that do not trust each other', () => {
should(tir.getDirectTrust(alice, bob)).equal(0);
should(tir.getDirectTrust(bob, alice)).equal(0);
should(tir.getDirectTrust("1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND", alice)).equal(0);
Copy link
Member

Choose a reason for hiding this comment

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

This is written in a more readable manner:

tir.getDirectTrust("1JDfVQkZxMvRwM3Lc6LkDrpX55Ldk3JqND", alice).should.equal(0)

@christosporios
Copy link
Member Author

Superseded by #7.

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.

2 participants