Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

MT safe bank and atomic multiple program execution #1309

Closed
wants to merge 2 commits into from

Conversation

aeyakovenko
Copy link
Member

@aeyakovenko aeyakovenko commented Sep 22, 2018

  • Make bank transaction processing MT safe. This also fixed a bug where a multiple spend within a batch was generating extra tokens :)
  • Allow for transactions to send multiple programs that will execute as one atomic operation
  • The above required a change to the transaction encoding and the ABI between the bank and the programs. (@garious there is an unsafe call to create a subset of mutable references that I couldn't find a way around https://github.com/solana-labs/solana/pull/1309/files#diff-7145777648f8c12caeb15b4e1755215cR438)
  • Store error codes for signatures

@garious @mvines before i rebase, let me know if this covers all the things we talked about last week. i know its a lot of changes but it all came together really well. Take a look at the new bank tests (https://github.com/solana-labs/solana/pull/1309/files#diff-7145777648f8c12caeb15b4e1755215cR872), that should showcase the improvements. Also, how do i update the sdk golden image tests? A big TODO is to pass multiple signatures in the TX, ill need some help from @sakridge with changing the cuda to support that

So main reason why it all came together in one change was that to fix the bug and still allow for context free contract execution the accounts need to be "locked" while they are being changed in the bank's pipeline. which means that spending from 1 to many would be bottlenecked by a single transaction thats in the pipeline.

The batching implementation is most of #1293, the main differences are

  1. keep the signed data in the transaction data
  2. use a program_keys vector in the Transaction
  3. use old names to reduce churn in other parts of the codebase

1 and 3 can be addressed later. For 2, this allows us to pack more things into one transaction. The reason why they are not stored as "keys" is because the engine doesn't distinguish RO from RW accounts in the transaction.

Bench results (mac)

  • bench_banking_stage_multi_programs ... bench: 28,720,294 ns/iter (+/- 9,772,506)
  • bench_banking_stage_multi_accounts ... bench: 13,838,541 ns/iter (+/- 8,367,130)

Multi programs process about 8*1024*5 programs per iteration (5 per tx that are spent atomically). 700ns per program.
Multi accounts processes 8*1024 transactions. 1.6us per atomic transaction

@rob-solana
Poh service is the entry generator and also what drives last_id registration into the bank.
https://github.com/solana-labs/solana/pull/1309/files#diff-a731213f65ed3e3b9e6129eb03dc9efeR121
There is a race between the entry output, and last_id's in the bank. The last_id queue has to move in lockstep with whats in the "ledger", which is the Entry output. With multiple threads driving the bank the output and updates to LastId queue need to be linearized.

@garious
Copy link
Contributor

garious commented Sep 24, 2018

I was surprised this PR doesn't reference #1293 and explain why it deviates from that design. Overloading of the term Program looks especially problematic.

For the golden image tests, there's a bunch of assert_eq!(actual, expected). When you change the binary format, those tests will fail. To fix them, copy the output from "left" (aka actual) back into the test.

@aeyakovenko
Copy link
Member Author

@garious left comment about #1293

@garious
Copy link
Contributor

garious commented Sep 24, 2018

Using old names to reduce churn makes this PR unnecessarily difficult to grok. Can you submit a separate PR that just changes the names to free up the namespace for this PR? instruction.rs -> budget_instruction.rs looks like the big one.

@aeyakovenko
Copy link
Member Author

@garious @mvines did we decide on names? #1293 has some of it documented, but its not the main point of that PR.


transactions.iter().for_each(|tx| {
let fund = Transaction::new(
&mint.keypair(),
Copy link
Contributor

Choose a reason for hiding this comment

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

This always transfers from the mint right? This is meant to transfer from multiple source accounts.

Copy link
Member Author

Choose a reason for hiding this comment

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

@sakridge yea, thats why the initial funding is synchronous

use std::thread::{self, Builder, JoinHandle};
use std::time::Duration;
use std::time::Instant;
use timing;
use transaction::Transaction;

pub const NUM_THREADS: usize = 8;
Copy link
Contributor

Choose a reason for hiding this comment

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

fixed number of threads?

Copy link
Member Author

Choose a reason for hiding this comment

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

yea, for now. we can make this dynamic later

Implement an mt safe bank and batch programs into single atomic transactions.

fix entry processing

fmt

once!

cleanup and remove clone

recv poh until response arrives

handle recverrors

fix benches

get rid of answer, use 10 threads because math is simpler

coalesce the extra thread switch

optimizing

cleanup

register faster

cleanup

fixed test

sdk serialize

fixed benches

rebase

program counter

fmt
@rob-solana
Copy link
Contributor

I'd move tick_producer thread into poh_service, then we'll have come full circle back to record_stage

let mut poh = self.poh.lock().unwrap();
poh.record(mixin)
Copy link
Contributor

Choose a reason for hiding this comment

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

drop this lock after record/tick()?

@rob-solana
Copy link
Contributor

I've been eyeballing this for a while and it looks like it'll work WRT lastidnotfound

@@ -15,6 +15,17 @@ pub const SIGNED_DATA_OFFSET: usize = size_of::<Signature>();
pub const SIG_OFFSET: usize = 0;
pub const PUB_KEY_OFFSET: usize = size_of::<Signature>() + size_of::<u64>();

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct Program {
Copy link
Contributor

Choose a reason for hiding this comment

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

If you rebase, you can rename this to Instruction now. The term Program is awkward here because that program_id field is not a unique identifier for instances of this struct, and because the stuff serialized in the userdata may be a new program or a call into an existing one. Maybe this should be an enum too?

@aeyakovenko
Copy link
Member Author

closing because most of this has been broken out into smaller PRs. Thank you for the feedback!

vkomenda pushed a commit to vkomenda/solana that referenced this pull request Aug 29, 2021
ryoqun pushed a commit to ryoqun/solana that referenced this pull request May 29, 2024
…s#1309)

* bump stable to v1.78.0

* bump nightly to 2024-05-02

* clippy: non_local_definitions
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants