From 80b2721cff78eb33e4ed566a996d2a8b580ff9ef Mon Sep 17 00:00:00 2001 From: chris erway Date: Tue, 26 Mar 2024 14:40:57 -0400 Subject: [PATCH 01/10] change AssembleBlock to return a bookkeeping.Block --- agreement/abstractions.go | 6 +++--- agreement/proposal.go | 23 +++++++++++++++++------ data/datatest/impls.go | 6 +++--- node/node.go | 6 +++--- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/agreement/abstractions.go b/agreement/abstractions.go index a22a3a0526..866c4733e6 100644 --- a/agreement/abstractions.go +++ b/agreement/abstractions.go @@ -72,10 +72,10 @@ 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 + // AssembleBlock produces a new Block which is suitable for proposal // at a given Round. // - // AssembleBlock should produce a ValidatedBlock for which the corresponding + // AssembleBlock should produce a bookkeeping.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. @@ -84,7 +84,7 @@ type BlockFactory interface { // produce a ValidatedBlock 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(basics.Round) (bookkeeping.Block, error) } // A Ledger represents the sequence of Entries agreed upon by the protocol. diff --git a/agreement/proposal.go b/agreement/proposal.go index ac29970b16..61416dc317 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -101,7 +101,18 @@ type proposal struct { validatedAt time.Duration } -func makeProposal(ve ValidatedBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { +// makeProposalFromBlock is called when making a new proposal message, from the output of AssembleBlock (from makeProposals -> proposalForBlock) +func makeProposalFromBlock(e bookkeeping.Block, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { + var payload unauthenticatedProposal + payload.Block = e + payload.SeedProof = pf + payload.OriginalPeriod = origPer + payload.OriginalProposer = origProp + return proposal{unauthenticatedProposal: payload} +} + +// makeProposal is called after successfully validating a proposal message, from 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 @@ -244,15 +255,15 @@ 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 bookkeeping.Block, 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) + blk = blk.WithSeed(newSeed) + proposal := makeProposalFromBlock(blk, seedProof, period, address) value := proposalValue{ OriginalPeriod: period, OriginalProposer: address, @@ -282,5 +293,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/data/datatest/impls.go b/data/datatest/impls.go index 7c9462d40d..4e70018958 100644 --- a/data/datatest/impls.go +++ b/data/datatest/impls.go @@ -53,15 +53,15 @@ type entryFactoryImpl struct { } // AssembleBlock implements Ledger.AssembleBlock. -func (i entryFactoryImpl) AssembleBlock(round basics.Round) (agreement.ValidatedBlock, error) { +func (i entryFactoryImpl) AssembleBlock(round basics.Round) (bookkeeping.Block, 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) + return bookkeeping.Block{}, fmt.Errorf("could not make proposals: could not read block from ledger at round %v: %v", round, err) } b := bookkeeping.MakeBlock(prev) b.RewardsState = prev.RewardsState - return validatedBlock{blk: &b}, nil + return b, nil } // WithSeed implements the agreement.ValidatedBlock interface. diff --git a/node/node.go b/node/node.go index 53c6c492a9..36513c665b 100644 --- a/node/node.go +++ b/node/node.go @@ -1308,7 +1308,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) (bookkeeping.Block, error) { deadline := time.Now().Add(node.config.ProposalAssemblyTime) lvb, err := node.transactionPool.AssembleBlock(round, deadline) if err != nil { @@ -1327,9 +1327,9 @@ func (node *AlgorandFullNode) AssembleBlock(round basics.Round) (agreement.Valid // the case where ledgerNextRound > round was not implemented here on purpose. This is the "normal case" where the // ledger was advancing faster then the agreement by the catchup. } - return nil, err + return bookkeeping.Block{}, err } - return validatedBlock{vb: lvb}, nil + return lvb.Block(), nil } // getOfflineClosedStatus will return an int with the appropriate bit(s) set if it is offline and/or online From 8ffc5ccb426610d867f6c29ad70b20dcb8110d7d Mon Sep 17 00:00:00 2001 From: chris erway Date: Wed, 27 Mar 2024 09:57:33 -0400 Subject: [PATCH 02/10] split ValidatedBlock and AssembledBlock interfaces --- agreement/abstractions.go | 23 +++++++++++++++-------- agreement/agreementtest/simulate_test.go | 4 ++-- agreement/common_test.go | 4 ++-- agreement/fuzzer/ledger_test.go | 4 ++-- agreement/proposal.go | 24 ++++++++++++------------ data/datatest/impls.go | 8 ++++---- node/node.go | 21 ++++++++++++++------- 7 files changed, 51 insertions(+), 37 deletions(-) diff --git a/agreement/abstractions.go b/agreement/abstractions.go index 866c4733e6..ef13a46ad3 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 } @@ -84,7 +77,21 @@ type BlockFactory interface { // produce a ValidatedBlock 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) (bookkeeping.Block, error) + AssembleBlock(basics.Round) (AssembledBlock, error) +} + +// An AssembledBlock represents a Block produced by a BlockFactory +// and can now be 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. + WithSeed(committee.Seed) AssembledBlock + + // 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..24628f2cec 100644 --- a/agreement/agreementtest/simulate_test.go +++ b/agreement/agreementtest/simulate_test.go @@ -79,7 +79,7 @@ func (b testValidatedBlock) Block() bookkeeping.Block { return b.Inside } -func (b testValidatedBlock) WithSeed(s committee.Seed) agreement.ValidatedBlock { +func (b testValidatedBlock) WithSeed(s committee.Seed) agreement.AssembledBlock { b.Inside.BlockHeader.Seed = s return b } @@ -94,7 +94,7 @@ type testBlockFactory struct { Owner int } -func (f testBlockFactory) AssembleBlock(r basics.Round) (agreement.ValidatedBlock, error) { +func (f testBlockFactory) AssembleBlock(r basics.Round) (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..d4afa552cb 100644 --- a/agreement/common_test.go +++ b/agreement/common_test.go @@ -165,7 +165,7 @@ func (b testValidatedBlock) Block() bookkeeping.Block { return b.Inside } -func (b testValidatedBlock) WithSeed(s committee.Seed) ValidatedBlock { +func (b testValidatedBlock) WithSeed(s committee.Seed) AssembledBlock { b.Inside.BlockHeader.Seed = s return b } @@ -180,7 +180,7 @@ type testBlockFactory struct { Owner int } -func (f testBlockFactory) AssembleBlock(r basics.Round) (ValidatedBlock, error) { +func (f testBlockFactory) AssembleBlock(r basics.Round) (AssembledBlock, error) { return testValidatedBlock{Inside: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{Round: r}}}, nil } diff --git a/agreement/fuzzer/ledger_test.go b/agreement/fuzzer/ledger_test.go index 00ba132294..a1e90e1abb 100644 --- a/agreement/fuzzer/ledger_test.go +++ b/agreement/fuzzer/ledger_test.go @@ -93,7 +93,7 @@ func (b testValidatedBlock) Block() bookkeeping.Block { return b.Inside } -func (b testValidatedBlock) WithSeed(s committee.Seed) agreement.ValidatedBlock { +func (b testValidatedBlock) WithSeed(s committee.Seed) agreement.AssembledBlock { b.Inside.BlockHeader.Seed = s return b } @@ -108,7 +108,7 @@ type testBlockFactory struct { Owner int } -func (f testBlockFactory) AssembleBlock(r basics.Round) (agreement.ValidatedBlock, error) { +func (f testBlockFactory) AssembleBlock(r basics.Round) (agreement.AssembledBlock, error) { return testValidatedBlock{Inside: bookkeeping.Block{BlockHeader: bookkeeping.BlockHeader{Round: r}}}, nil } diff --git a/agreement/proposal.go b/agreement/proposal.go index 61416dc317..f74573ff6d 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -101,25 +101,25 @@ type proposal struct { validatedAt time.Duration } -// makeProposalFromBlock is called when making a new proposal message, from the output of AssembleBlock (from makeProposals -> proposalForBlock) -func makeProposalFromBlock(e bookkeeping.Block, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { +func makeUnauthenticatedProposal(e bookkeeping.Block, pf crypto.VrfProof, origPer period, origProp basics.Address) unauthenticatedProposal { var payload unauthenticatedProposal payload.Block = e payload.SeedProof = pf payload.OriginalPeriod = origPer payload.OriginalProposer = origProp - return proposal{unauthenticatedProposal: payload} + return payload +} + +// makeProposalFromAssembledBlock is called when making a new proposal message, from the output of AssembleBlock (from makeProposals -> proposalForBlock) +func makeProposalFromAssembledBlock(blk AssembledBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { + e := blk.Block() + return proposal{unauthenticatedProposal: makeUnauthenticatedProposal(e, pf, origPer, origProp)} } // makeProposal is called after successfully validating a proposal message, from 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: makeUnauthenticatedProposal(e, pf, origPer, origProp), ve: ve} } func (p proposal) u() unauthenticatedProposal { @@ -255,15 +255,15 @@ func verifyNewSeed(p unauthenticatedProposal, ledger LedgerReader) error { return nil } -func proposalForBlock(address basics.Address, vrf *crypto.VRFSecrets, blk bookkeeping.Block, period period, ledger LedgerReader) (proposal, proposalValue, error) { - rnd := blk.Round() +func proposalForBlock(address basics.Address, vrf *crypto.VRFSecrets, blk AssembledBlock, period period, ledger LedgerReader) (proposal, proposalValue, error) { + rnd := blk.Block().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) } blk = blk.WithSeed(newSeed) - proposal := makeProposalFromBlock(blk, seedProof, period, address) + proposal := makeProposalFromAssembledBlock(blk, seedProof, period, address) value := proposalValue{ OriginalPeriod: period, OriginalProposer: address, diff --git a/data/datatest/impls.go b/data/datatest/impls.go index 4e70018958..e0cb4ffc51 100644 --- a/data/datatest/impls.go +++ b/data/datatest/impls.go @@ -53,19 +53,19 @@ type entryFactoryImpl struct { } // AssembleBlock implements Ledger.AssembleBlock. -func (i entryFactoryImpl) AssembleBlock(round basics.Round) (bookkeeping.Block, error) { +func (i entryFactoryImpl) AssembleBlock(round basics.Round) (agreement.AssembledBlock, error) { prev, err := i.l.BlockHdr(round - 1) if err != nil { - return bookkeeping.Block{}, fmt.Errorf("could not make proposals: could not read block from ledger at round %v: %v", round, err) + return nil, fmt.Errorf("could not make proposals: could not read block from ledger at round %v: %v", round, err) } b := bookkeeping.MakeBlock(prev) b.RewardsState = prev.RewardsState - return b, nil + return validatedBlock{blk: &b}, nil } // WithSeed implements the agreement.ValidatedBlock interface. -func (ve validatedBlock) WithSeed(s committee.Seed) agreement.ValidatedBlock { +func (ve validatedBlock) WithSeed(s committee.Seed) agreement.AssembledBlock { newblock := ve.blk.WithSeed(s) return validatedBlock{blk: &newblock} } diff --git a/node/node.go b/node/node.go index 36513c665b..0ae26bfb15 100644 --- a/node/node.go +++ b/node/node.go @@ -1295,12 +1295,19 @@ 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 } +// WithSeed satisfies the agreement.AssembledBlock interface. +func (ab assembledBlock) WithSeed(s committee.Seed) agreement.AssembledBlock { + blk := ab.blk.WithSeed(s) + return assembledBlock{blk: blk} +} + +// Block satisfies the agreement.AssembledBlock interface. +func (ab assembledBlock) Block() bookkeeping.Block { return ab.blk } + // Block satisfies the agreement.ValidatedBlock interface. func (vb validatedBlock) Block() bookkeeping.Block { blk := vb.vb.Block() @@ -1308,7 +1315,7 @@ func (vb validatedBlock) Block() bookkeeping.Block { } // AssembleBlock implements Ledger.AssembleBlock. -func (node *AlgorandFullNode) AssembleBlock(round basics.Round) (bookkeeping.Block, error) { +func (node *AlgorandFullNode) AssembleBlock(round basics.Round) (agreement.AssembledBlock, error) { deadline := time.Now().Add(node.config.ProposalAssemblyTime) lvb, err := node.transactionPool.AssembleBlock(round, deadline) if err != nil { @@ -1327,9 +1334,9 @@ func (node *AlgorandFullNode) AssembleBlock(round basics.Round) (bookkeeping.Blo // the case where ledgerNextRound > round was not implemented here on purpose. This is the "normal case" where the // ledger was advancing faster then the agreement by the catchup. } - return bookkeeping.Block{}, err + return nil, err } - return lvb.Block(), 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 From 2b97b66775c145be1754156797303f5836813356 Mon Sep 17 00:00:00 2001 From: chris erway Date: Wed, 27 Mar 2024 10:20:35 -0400 Subject: [PATCH 03/10] tweak comments --- agreement/abstractions.go | 6 +++--- agreement/proposal.go | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/agreement/abstractions.go b/agreement/abstractions.go index ef13a46ad3..70988f0a22 100644 --- a/agreement/abstractions.go +++ b/agreement/abstractions.go @@ -65,16 +65,16 @@ 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 Block which is suitable for proposal + // AssembleBlock produces a new AssembledBlock which is suitable for proposal // at a given Round. // - // AssembleBlock should produce a bookkeeping.Block 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) (AssembledBlock, error) diff --git a/agreement/proposal.go b/agreement/proposal.go index f74573ff6d..08bb949531 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -101,25 +101,28 @@ type proposal struct { validatedAt time.Duration } -func makeUnauthenticatedProposal(e bookkeeping.Block, pf crypto.VrfProof, origPer period, origProp basics.Address) unauthenticatedProposal { +// makeProposalFromAssembledBlock is called when making a new proposal message, +// using the output of AssembleBlock (from makeProposals -> proposalForBlock) +func makeProposalFromAssembledBlock(blk AssembledBlock, 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 payload -} - -// makeProposalFromAssembledBlock is called when making a new proposal message, from the output of AssembleBlock (from makeProposals -> proposalForBlock) -func makeProposalFromAssembledBlock(blk AssembledBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { - e := blk.Block() - return proposal{unauthenticatedProposal: makeUnauthenticatedProposal(e, pf, origPer, origProp)} + return proposal{unauthenticatedProposal: payload} } -// makeProposal is called after successfully validating a proposal message, from the output of BlockValidator.Validate (from unauthenticatedProposal.validate) +// 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() - return proposal{unauthenticatedProposal: makeUnauthenticatedProposal(e, pf, origPer, origProp), ve: ve} + var payload unauthenticatedProposal + payload.Block = e + payload.SeedProof = pf + payload.OriginalPeriod = origPer + payload.OriginalProposer = origProp + return proposal{unauthenticatedProposal: payload, ve: ve} // store ve to use when calling Ledger.EnsureValidatedBlock } func (p proposal) u() unauthenticatedProposal { From 569a5c8dacaadd401f38e10f3f868fc1923b73f7 Mon Sep 17 00:00:00 2001 From: chris erway Date: Wed, 27 Mar 2024 10:35:47 -0400 Subject: [PATCH 04/10] make linter happy --- agreement/proposal.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/agreement/proposal.go b/agreement/proposal.go index 08bb949531..066a76eed2 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -266,14 +266,14 @@ func proposalForBlock(address basics.Address, vrf *crypto.VRFSecrets, blk Assemb } blk = blk.WithSeed(newSeed) - proposal := makeProposalFromAssembledBlock(blk, seedProof, period, address) + prop := makeProposalFromAssembledBlock(blk, 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. From af2412d9215e3c30bee1366dc18ddab71b1ae7ca Mon Sep 17 00:00:00 2001 From: chris erway Date: Wed, 27 Mar 2024 12:51:41 -0400 Subject: [PATCH 05/10] fix comment --- data/datatest/impls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/datatest/impls.go b/data/datatest/impls.go index e0cb4ffc51..f3b5f23a8e 100644 --- a/data/datatest/impls.go +++ b/data/datatest/impls.go @@ -64,7 +64,7 @@ func (i entryFactoryImpl) AssembleBlock(round basics.Round) (agreement.Assembled return validatedBlock{blk: &b}, nil } -// WithSeed implements the agreement.ValidatedBlock interface. +// WithSeed implements the agreement.AssembledBlock interface. func (ve validatedBlock) WithSeed(s committee.Seed) agreement.AssembledBlock { newblock := ve.blk.WithSeed(s) return validatedBlock{blk: &newblock} From bdda2a5469d53b2e38b5913d394f3bc33d9c567f Mon Sep 17 00:00:00 2001 From: chris erway Date: Mon, 1 Apr 2024 13:29:23 -0400 Subject: [PATCH 06/10] split AssembledBlock/ProposableBlock --- agreement/abstractions.go | 11 ++++++++--- agreement/proposal.go | 6 +++--- agreement/pseudonode.go | 2 +- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/agreement/abstractions.go b/agreement/abstractions.go index 70988f0a22..43e74a67bd 100644 --- a/agreement/abstractions.go +++ b/agreement/abstractions.go @@ -77,19 +77,24 @@ type BlockFactory interface { // 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) (AssembledBlock, error) + AssembleBlock(rnd basics.Round, partAddresses []basics.Address) (AssembledBlock, error) } // An AssembledBlock represents a Block produced by a BlockFactory -// and can now be proposed by agreement. +// 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. - WithSeed(committee.Seed) AssembledBlock + FinalizeBlock(seed committee.Seed, proposer basics.Address) ProposableBlock +} +// 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 } diff --git a/agreement/proposal.go b/agreement/proposal.go index 066a76eed2..e067471224 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -103,7 +103,7 @@ type proposal struct { // makeProposalFromAssembledBlock is called when making a new proposal message, // using the output of AssembleBlock (from makeProposals -> proposalForBlock) -func makeProposalFromAssembledBlock(blk AssembledBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { +func makeProposalFromAssembledBlock(blk ProposableBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { e := blk.Block() var payload unauthenticatedProposal payload.Block = e @@ -265,8 +265,8 @@ func proposalForBlock(address basics.Address, vrf *crypto.VRFSecrets, blk Assemb return proposal{}, proposalValue{}, fmt.Errorf("proposalForBlock: could not derive new seed: %v", err) } - blk = blk.WithSeed(newSeed) - prop := makeProposalFromAssembledBlock(blk, seedProof, period, address) + proposableBlock := blk.FinalizeBlock(newSeed, address) + prop := makeProposalFromAssembledBlock(proposableBlock, seedProof, period, address) value := proposalValue{ OriginalPeriod: period, OriginalProposer: address, diff --git a/agreement/pseudonode.go b/agreement/pseudonode.go index e0bfb326bd..51d565bf61 100644 --- a/agreement/pseudonode.go +++ b/agreement/pseudonode.go @@ -284,7 +284,7 @@ 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) + ve, err := n.factory.AssembleBlock(round, accounts) if err != nil { if err != ErrAssembleBlockRoundStale { n.log.Errorf("pseudonode.makeProposals: could not generate a proposal for round %d: %v", round, err) From 421fa47d40d6ad81012f7386d1c4004be2b67d47 Mon Sep 17 00:00:00 2001 From: chris erway Date: Mon, 1 Apr 2024 15:15:34 -0400 Subject: [PATCH 07/10] make tests compile --- agreement/abstractions.go | 2 ++ agreement/agreementtest/simulate_test.go | 8 ++++++-- agreement/common_test.go | 13 +++++++++---- agreement/fuzzer/ledger_test.go | 8 ++++++-- agreement/player_permutation_test.go | 4 +++- agreement/proposal.go | 2 +- agreement/proposalStore_test.go | 16 ++++++++-------- agreement/proposal_test.go | 6 +++--- agreement/pseudonode.go | 6 +++++- node/node.go | 15 +++++++++++---- 10 files changed, 54 insertions(+), 26 deletions(-) diff --git a/agreement/abstractions.go b/agreement/abstractions.go index 43e74a67bd..3d4311598a 100644 --- a/agreement/abstractions.go +++ b/agreement/abstractions.go @@ -89,6 +89,8 @@ type AssembledBlock interface { // 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, diff --git a/agreement/agreementtest/simulate_test.go b/agreement/agreementtest/simulate_test.go index 24628f2cec..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.AssembledBlock { +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.AssembledBlock, 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 d4afa552cb..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) AssembledBlock { +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) (AssembledBlock, 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 a1e90e1abb..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.AssembledBlock { +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.AssembledBlock, 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 e067471224..067be0eb9a 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -259,7 +259,7 @@ func verifyNewSeed(p unauthenticatedProposal, ledger LedgerReader) error { } func proposalForBlock(address basics.Address, vrf *crypto.VRFSecrets, blk AssembledBlock, period period, ledger LedgerReader) (proposal, proposalValue, error) { - rnd := blk.Block().Round() + 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) 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 51d565bf61..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, accounts) + 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/node/node.go b/node/node.go index 0ae26bfb15..d0a92fa5ca 100644 --- a/node/node.go +++ b/node/node.go @@ -1299,14 +1299,21 @@ type assembledBlock struct { blk bookkeeping.Block } +type proposableBlock struct { + blk bookkeeping.Block +} + // WithSeed satisfies the agreement.AssembledBlock interface. -func (ab assembledBlock) WithSeed(s committee.Seed) agreement.AssembledBlock { +func (ab assembledBlock) FinalizeBlock(s committee.Seed, addr basics.Address) agreement.ProposableBlock { blk := ab.blk.WithSeed(s) - return assembledBlock{blk: blk} + return proposableBlock{blk: blk} } // Block satisfies the agreement.AssembledBlock interface. -func (ab assembledBlock) Block() bookkeeping.Block { return ab.blk } +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 { @@ -1315,7 +1322,7 @@ func (vb validatedBlock) Block() bookkeeping.Block { } // AssembleBlock implements Ledger.AssembleBlock. -func (node *AlgorandFullNode) AssembleBlock(round basics.Round) (agreement.AssembledBlock, 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 { From 09a83646965e17b4d6bfed41a9545233d2748156 Mon Sep 17 00:00:00 2001 From: chris erway Date: Tue, 2 Apr 2024 09:21:29 -0400 Subject: [PATCH 08/10] rename makeProposalFromAssembledBlock --- agreement/proposal.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/agreement/proposal.go b/agreement/proposal.go index 067be0eb9a..4d1d0fc9ff 100644 --- a/agreement/proposal.go +++ b/agreement/proposal.go @@ -101,9 +101,9 @@ type proposal struct { validatedAt time.Duration } -// makeProposalFromAssembledBlock is called when making a new proposal message, +// makeProposalFromProposableBlock is called when making a new proposal message, // using the output of AssembleBlock (from makeProposals -> proposalForBlock) -func makeProposalFromAssembledBlock(blk ProposableBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { +func makeProposalFromProposableBlock(blk ProposableBlock, pf crypto.VrfProof, origPer period, origProp basics.Address) proposal { e := blk.Block() var payload unauthenticatedProposal payload.Block = e @@ -266,7 +266,7 @@ func proposalForBlock(address basics.Address, vrf *crypto.VRFSecrets, blk Assemb } proposableBlock := blk.FinalizeBlock(newSeed, address) - prop := makeProposalFromAssembledBlock(proposableBlock, seedProof, period, address) + prop := makeProposalFromProposableBlock(proposableBlock, seedProof, period, address) value := proposalValue{ OriginalPeriod: period, OriginalProposer: address, From d2745e534bef88accb8b45aa2fef13d9db8a60ca Mon Sep 17 00:00:00 2001 From: chris erway Date: Tue, 2 Apr 2024 09:26:29 -0400 Subject: [PATCH 09/10] update data/datatest BlockFactory impl --- data/datatest/impls.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/data/datatest/impls.go b/data/datatest/impls.go index f3b5f23a8e..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.AssembledBlock, 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) @@ -65,7 +65,7 @@ func (i entryFactoryImpl) AssembleBlock(round basics.Round) (agreement.Assembled } // WithSeed implements the agreement.AssembledBlock interface. -func (ve validatedBlock) WithSeed(s committee.Seed) agreement.AssembledBlock { +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 } From 5a19b26045ab3aa1ee0ea12df60febf062bc8c02 Mon Sep 17 00:00:00 2001 From: chris erway Date: Tue, 2 Apr 2024 09:29:40 -0400 Subject: [PATCH 10/10] update BlockFactory comment --- agreement/abstractions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agreement/abstractions.go b/agreement/abstractions.go index 3d4311598a..b63b3f33ab 100644 --- a/agreement/abstractions.go +++ b/agreement/abstractions.go @@ -65,8 +65,8 @@ 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 AssembledBlock 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 block for which the corresponding // BlockValidator validates (i.e. for which BlockValidator.Validate