diff --git a/agreement/abstractions.go b/agreement/abstractions.go index a22a3a0526..b63b3f33ab 100644 --- a/agreement/abstractions.go +++ b/agreement/abstractions.go @@ -54,13 +54,6 @@ type BlockValidator interface { // and can now be recorded in the ledger. This is an optimized version of // calling EnsureBlock() on the Ledger. type ValidatedBlock interface { - // WithSeed creates a copy of this ValidatedBlock with its - // cryptographically random seed set to the given value. - // - // Calls to Seed() or to Digest() on the copy's Block must - // reflect the value of the new seed. - WithSeed(committee.Seed) ValidatedBlock - // Block returns the underlying block that has been validated. Block() bookkeeping.Block } @@ -72,19 +65,40 @@ var ErrAssembleBlockRoundStale = errors.New("requested round for AssembleBlock i // An BlockFactory produces an Block which is suitable for proposal for a given // Round. type BlockFactory interface { - // AssembleBlock produces a new ValidatedBlock which is suitable for proposal - // at a given Round. + // AssembleBlock produces a new AssembledBlock for a given Round. + // It must be finalized before proposed by agreement. // - // AssembleBlock should produce a ValidatedBlock for which the corresponding + // AssembleBlock should produce a block for which the corresponding // BlockValidator validates (i.e. for which BlockValidator.Validate // returns true). If an insufficient number of nodes can assemble valid // entries, the agreement protocol may lose liveness. // // AssembleBlock may return an error if the BlockFactory is unable to - // produce a ValidatedBlock for the given round. If an insufficient number of + // produce a AssembledBlock for the given round. If an insufficient number of // nodes on the network can assemble entries, the agreement protocol may // lose liveness. - AssembleBlock(basics.Round) (ValidatedBlock, error) + AssembleBlock(rnd basics.Round, partAddresses []basics.Address) (AssembledBlock, error) +} + +// An AssembledBlock represents a Block produced by a BlockFactory +// and must be finalized before being proposed by agreement. +type AssembledBlock interface { + // WithSeed creates a copy of this AssembledBlock with its + // cryptographically random seed set to the given value. + // + // Calls to Seed() or to Digest() on the copy's Block must + // reflect the value of the new seed. + FinalizeBlock(seed committee.Seed, proposer basics.Address) ProposableBlock + + Round() basics.Round +} + +// An ProposableBlock represents a Block produced by a BlockFactory, +// that was later finalized by providing the seed and the proposer, +// and can now be proposed by agreement. +type ProposableBlock interface { + // Block returns the underlying block that has been assembled. + Block() bookkeeping.Block } // A Ledger represents the sequence of Entries agreed upon by the protocol. diff --git a/agreement/agreementtest/simulate_test.go b/agreement/agreementtest/simulate_test.go index a0afca4d46..fbd12a35bb 100644 --- a/agreement/agreementtest/simulate_test.go +++ b/agreement/agreementtest/simulate_test.go @@ -79,7 +79,11 @@ func (b testValidatedBlock) Block() bookkeeping.Block { return b.Inside } -func (b testValidatedBlock) WithSeed(s committee.Seed) agreement.ValidatedBlock { +func (b testValidatedBlock) Round() basics.Round { + return b.Inside.Round() +} + +func (b testValidatedBlock) FinalizeBlock(s committee.Seed, proposer basics.Address) agreement.ProposableBlock { b.Inside.BlockHeader.Seed = s return b } @@ -94,7 +98,7 @@ type testBlockFactory struct { Owner int } -func (f testBlockFactory) AssembleBlock(r basics.Round) (agreement.ValidatedBlock, error) { +func (f testBlockFactory) AssembleBlock(r basics.Round, _ []basics.Address) (agreement.AssembledBlock, error) { return testValidatedBlock{Inside: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{Round: r}}}, nil } diff --git a/agreement/common_test.go b/agreement/common_test.go index 9ec85618e8..de5297b40a 100644 --- a/agreement/common_test.go +++ b/agreement/common_test.go @@ -165,7 +165,11 @@ func (b testValidatedBlock) Block() bookkeeping.Block { return b.Inside } -func (b testValidatedBlock) WithSeed(s committee.Seed) ValidatedBlock { +func (b testValidatedBlock) Round() basics.Round { + return b.Inside.Round() +} + +func (b testValidatedBlock) FinalizeBlock(s committee.Seed, _ basics.Address) ProposableBlock { b.Inside.BlockHeader.Seed = s return b } @@ -180,7 +184,7 @@ type testBlockFactory struct { Owner int } -func (f testBlockFactory) AssembleBlock(r basics.Round) (ValidatedBlock, error) { +func (f testBlockFactory) AssembleBlock(r basics.Round, _ []basics.Address) (AssembledBlock, error) { return testValidatedBlock{Inside: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{Round: r}}}, nil } @@ -413,7 +417,7 @@ type testAccountData struct { } func makeProposalsTesting(accs testAccountData, round basics.Round, period period, factory BlockFactory, ledger Ledger) (ps []proposal, vs []vote) { - ve, err := factory.AssembleBlock(round) + ve, err := factory.AssembleBlock(round, accs.addresses) if err != nil { logging.Base().Errorf("Could not generate a proposal for round %d: %v", round, err) return nil, nil @@ -525,8 +529,9 @@ func (v *voteMakerHelper) MakeRandomProposalValue() *proposalValue { func (v *voteMakerHelper) MakeRandomProposalPayload(t *testing.T, r round) (*proposal, *proposalValue) { f := testBlockFactory{Owner: 1} - ve, err := f.AssembleBlock(r) + ae, err := f.AssembleBlock(r, nil) require.NoError(t, err) + ve := ae.FinalizeBlock(committee.Seed{}, basics.Address{}) var payload unauthenticatedProposal payload.Block = ve.Block() diff --git a/agreement/fuzzer/ledger_test.go b/agreement/fuzzer/ledger_test.go index 00ba132294..ca14e01eec 100644 --- a/agreement/fuzzer/ledger_test.go +++ b/agreement/fuzzer/ledger_test.go @@ -93,7 +93,11 @@ func (b testValidatedBlock) Block() bookkeeping.Block { return b.Inside } -func (b testValidatedBlock) WithSeed(s committee.Seed) agreement.ValidatedBlock { +func (b testValidatedBlock) Round() basics.Round { + return b.Inside.Round() +} + +func (b testValidatedBlock) FinalizeBlock(s committee.Seed, _ basics.Address) agreement.ProposableBlock { b.Inside.BlockHeader.Seed = s return b } @@ -108,7 +112,7 @@ type testBlockFactory struct { Owner int } -func (f testBlockFactory) AssembleBlock(r basics.Round) (agreement.ValidatedBlock, error) { +func (f testBlockFactory) AssembleBlock(r basics.Round, _ []basics.Address) (agreement.AssembledBlock, error) { return testValidatedBlock{Inside: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{Round: r}}}, nil } diff --git a/agreement/player_permutation_test.go b/agreement/player_permutation_test.go index e41195d1f1..b2cf81541d 100644 --- a/agreement/player_permutation_test.go +++ b/agreement/player_permutation_test.go @@ -25,13 +25,15 @@ import ( "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/crypto" "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/committee" "github.com/algorand/go-algorand/protocol" "github.com/algorand/go-algorand/test/partitiontest" ) func makeRandomProposalPayload(r round) *proposal { f := testBlockFactory{Owner: 1} - ve, _ := f.AssembleBlock(r) + ae, _ := f.AssembleBlock(r, nil) + ve := ae.FinalizeBlock(committee.Seed{}, basics.Address{}) var payload unauthenticatedProposal payload.Block = ve.Block() diff --git a/agreement/proposal.go b/agreement/proposal.go index ac29970b16..4d1d0fc9ff 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -101,14 +101,28 @@ type proposal struct { validatedAt time.Duration } -func makeProposal(ve ValidatedBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { +// makeProposalFromProposableBlock is called when making a new proposal message, +// using the output of AssembleBlock (from makeProposals -> proposalForBlock) +func makeProposalFromProposableBlock(blk ProposableBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { + e := blk.Block() + var payload unauthenticatedProposal + payload.Block = e + payload.SeedProof = pf + payload.OriginalPeriod = origPer + payload.OriginalProposer = origProp + return proposal{unauthenticatedProposal: payload} +} + +// makeProposalFromValidatedBlock is called after successfully validating a proposal message, +// using the output of BlockValidator.Validate (from unauthenticatedProposal.validate) +func makeProposalFromValidatedBlock(ve ValidatedBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { e := ve.Block() var payload unauthenticatedProposal payload.Block = e payload.SeedProof = pf payload.OriginalPeriod = origPer payload.OriginalProposer = origProp - return proposal{unauthenticatedProposal: payload, ve: ve} + return proposal{unauthenticatedProposal: payload, ve: ve} // store ve to use when calling Ledger.EnsureValidatedBlock } func (p proposal) u() unauthenticatedProposal { @@ -244,22 +258,22 @@ func verifyNewSeed(p unauthenticatedProposal, ledger LedgerReader) error { return nil } -func proposalForBlock(address basics.Address, vrf *crypto.VRFSecrets, ve ValidatedBlock, period period, ledger LedgerReader) (proposal, proposalValue, error) { - rnd := ve.Block().Round() +func proposalForBlock(address basics.Address, vrf *crypto.VRFSecrets, blk AssembledBlock, period period, ledger LedgerReader) (proposal, proposalValue, error) { + rnd := blk.Round() newSeed, seedProof, err := deriveNewSeed(address, vrf, rnd, period, ledger) if err != nil { return proposal{}, proposalValue{}, fmt.Errorf("proposalForBlock: could not derive new seed: %v", err) } - ve = ve.WithSeed(newSeed) - proposal := makeProposal(ve, seedProof, period, address) + proposableBlock := blk.FinalizeBlock(newSeed, address) + prop := makeProposalFromProposableBlock(proposableBlock, seedProof, period, address) value := proposalValue{ OriginalPeriod: period, OriginalProposer: address, - BlockDigest: proposal.Block.Digest(), - EncodingDigest: crypto.HashObj(proposal), + BlockDigest: prop.Block.Digest(), + EncodingDigest: crypto.HashObj(prop), } - return proposal, value, nil + return prop, value, nil } // validate returns true if the proposal is valid. @@ -282,5 +296,5 @@ func (p unauthenticatedProposal) validate(ctx context.Context, current round, le return invalid, fmt.Errorf("EntryValidator rejected entry: %v", err) } - return makeProposal(ve, p.SeedProof, p.OriginalPeriod, p.OriginalProposer), nil + return makeProposalFromValidatedBlock(ve, p.SeedProof, p.OriginalPeriod, p.OriginalProposer), nil } diff --git a/agreement/proposalStore_test.go b/agreement/proposalStore_test.go index 2ce8c23753..d434a3cca8 100644 --- a/agreement/proposalStore_test.go +++ b/agreement/proposalStore_test.go @@ -64,7 +64,7 @@ func TestBlockAssemblerPipeline(t *testing.T) { round := player.Round period := player.Period - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", round, err) accountIndex := 0 @@ -132,7 +132,7 @@ func TestBlockAssemblerBind(t *testing.T) { player, _, accounts, factory, ledger := testSetup(0) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 @@ -200,7 +200,7 @@ func TestBlockAssemblerAuthenticator(t *testing.T) { player, _, accounts, factory, ledger := testSetup(0) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 proposalPayload, _, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, player.Period, ledger) @@ -266,7 +266,7 @@ func TestBlockAssemblerTrim(t *testing.T) { player, _, accounts, factory, ledger := testSetup(0) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 proposalPayload, _, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, player.Period, ledger) @@ -339,7 +339,7 @@ func TestProposalStoreT(t *testing.T) { player, _, accounts, factory, ledger := testSetup(0) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 proposalPayload, proposalV, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, player.Period, ledger) @@ -413,7 +413,7 @@ func TestProposalStoreUnderlying(t *testing.T) { player, _, accounts, factory, ledger := testSetup(0) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 proposalPayload, proposalV, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, player.Period, ledger) @@ -477,7 +477,7 @@ func TestProposalStoreHandle(t *testing.T) { proposalVoteEventBatch, proposalPayloadEventBatch, _ := generateProposalEvents(t, player, accounts, factory, ledger) - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 _, proposalV0, _ := proposalForBlock(accounts.addresses[accountIndex], accounts.vrfs[accountIndex], testBlockFactory, player.Period, ledger) @@ -661,7 +661,7 @@ func TestProposalStoreGetPinnedValue(t *testing.T) { // create proposal Store player, router, accounts, factory, ledger := testPlayerSetup() - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", player.Round, err) accountIndex := 0 // create a route handler for the proposal store diff --git a/agreement/proposal_test.go b/agreement/proposal_test.go index 98cb177073..31b8d83050 100644 --- a/agreement/proposal_test.go +++ b/agreement/proposal_test.go @@ -46,7 +46,7 @@ func testSetup(periodCount uint64) (player, rootRouter, testAccountData, testBlo } func createProposalsTesting(accs testAccountData, round basics.Round, period period, factory BlockFactory, ledger Ledger) (ps []proposal, vs []vote) { - ve, err := factory.AssembleBlock(round) + ve, err := factory.AssembleBlock(round, accs.addresses) if err != nil { logging.Base().Errorf("Could not generate a proposal for round %d: %v", round, err) return nil, nil @@ -122,7 +122,7 @@ func TestProposalFunctions(t *testing.T) { player, _, accs, factory, ledger := testSetup(0) round := player.Round period := player.Period - ve, err := factory.AssembleBlock(player.Round) + ve, err := factory.AssembleBlock(player.Round, accs.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", round, err) validator := testBlockValidator{} @@ -162,7 +162,7 @@ func TestProposalUnauthenticated(t *testing.T) { round := player.Round period := player.Period - testBlockFactory, err := factory.AssembleBlock(player.Round) + testBlockFactory, err := factory.AssembleBlock(player.Round, accounts.addresses) require.NoError(t, err, "Could not generate a proposal for round %d: %v", round, err) validator := testBlockValidator{} diff --git a/agreement/pseudonode.go b/agreement/pseudonode.go index e0bfb326bd..b86a7151cb 100644 --- a/agreement/pseudonode.go +++ b/agreement/pseudonode.go @@ -284,7 +284,11 @@ func (n asyncPseudonode) makePseudonodeVerifier(voteVerifier *AsyncVoteVerifier) // makeProposals creates a slice of block proposals for the given round and period. func (n asyncPseudonode) makeProposals(round basics.Round, period period, accounts []account.ParticipationRecordForRound) ([]proposal, []unauthenticatedVote) { - ve, err := n.factory.AssembleBlock(round) + acctAddresses := make([]basics.Address, len(accounts)) + for i := range accounts { + acctAddresses[i] = accounts[i].Account + } + ve, err := n.factory.AssembleBlock(round, acctAddresses) if err != nil { if err != ErrAssembleBlockRoundStale { n.log.Errorf("pseudonode.makeProposals: could not generate a proposal for round %d: %v", round, err) diff --git a/data/datatest/impls.go b/data/datatest/impls.go index 7c9462d40d..ddbc488b9f 100644 --- a/data/datatest/impls.go +++ b/data/datatest/impls.go @@ -53,7 +53,7 @@ type entryFactoryImpl struct { } // AssembleBlock implements Ledger.AssembleBlock. -func (i entryFactoryImpl) AssembleBlock(round basics.Round) (agreement.ValidatedBlock, error) { +func (i entryFactoryImpl) AssembleBlock(round basics.Round, _ []basics.Address) (agreement.AssembledBlock, error) { prev, err := i.l.BlockHdr(round - 1) if err != nil { return nil, fmt.Errorf("could not make proposals: could not read block from ledger at round %v: %v", round, err) @@ -64,8 +64,8 @@ func (i entryFactoryImpl) AssembleBlock(round basics.Round) (agreement.Validated return validatedBlock{blk: &b}, nil } -// WithSeed implements the agreement.ValidatedBlock interface. -func (ve validatedBlock) WithSeed(s committee.Seed) agreement.ValidatedBlock { +// WithSeed implements the agreement.AssembledBlock interface. +func (ve validatedBlock) FinalizeBlock(s committee.Seed, _ basics.Address) agreement.ProposableBlock { newblock := ve.blk.WithSeed(s) return validatedBlock{blk: &newblock} } @@ -75,6 +75,11 @@ func (ve validatedBlock) Block() bookkeeping.Block { return *ve.blk } +// Round implements the agreement.AssembledBlock interface. +func (ve validatedBlock) Round() basics.Round { + return ve.blk.Round() +} + type ledgerImpl struct { l *data.Ledger } diff --git a/node/node.go b/node/node.go index 53c6c492a9..d0a92fa5ca 100644 --- a/node/node.go +++ b/node/node.go @@ -1295,12 +1295,26 @@ type validatedBlock struct { vb *ledgercore.ValidatedBlock } -// WithSeed satisfies the agreement.ValidatedBlock interface. -func (vb validatedBlock) WithSeed(s committee.Seed) agreement.ValidatedBlock { - lvb := vb.vb.WithSeed(s) - return validatedBlock{vb: &lvb} +type assembledBlock struct { + blk bookkeeping.Block } +type proposableBlock struct { + blk bookkeeping.Block +} + +// WithSeed satisfies the agreement.AssembledBlock interface. +func (ab assembledBlock) FinalizeBlock(s committee.Seed, addr basics.Address) agreement.ProposableBlock { + blk := ab.blk.WithSeed(s) + return proposableBlock{blk: blk} +} + +// Block satisfies the agreement.AssembledBlock interface. +func (ab assembledBlock) Round() basics.Round { return ab.blk.Round() } + +// Block satisfies the agreement.ProposableBlock interface. +func (pb proposableBlock) Block() bookkeeping.Block { return pb.blk } + // Block satisfies the agreement.ValidatedBlock interface. func (vb validatedBlock) Block() bookkeeping.Block { blk := vb.vb.Block() @@ -1308,7 +1322,7 @@ func (vb validatedBlock) Block() bookkeeping.Block { } // AssembleBlock implements Ledger.AssembleBlock. -func (node *AlgorandFullNode) AssembleBlock(round basics.Round) (agreement.ValidatedBlock, error) { +func (node *AlgorandFullNode) AssembleBlock(round basics.Round, addrs []basics.Address) (agreement.AssembledBlock, error) { deadline := time.Now().Add(node.config.ProposalAssemblyTime) lvb, err := node.transactionPool.AssembleBlock(round, deadline) if err != nil { @@ -1329,7 +1343,7 @@ func (node *AlgorandFullNode) AssembleBlock(round basics.Round) (agreement.Valid } return nil, err } - return validatedBlock{vb: lvb}, nil + return assembledBlock{blk: lvb.Block()}, nil } // getOfflineClosedStatus will return an int with the appropriate bit(s) set if it is offline and/or online