Skip to content

Commit

Permalink
Block state root and receipt cid now reference result of parent tip s…
Browse files Browse the repository at this point in the history
…et message processing (#3622)

* use parent state root in block generate [wip]

* StateRoot and MessageReceipts in block header now refer to parent tip set

* remove unused ProcessBlock

* move state root and receipt root to validateMining
  • Loading branch information
acruikshank authored Nov 4, 2019
1 parent 6ac4b83 commit c6b771c
Show file tree
Hide file tree
Showing 19 changed files with 234 additions and 366 deletions.
10 changes: 0 additions & 10 deletions cmd/go-filecoin/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ all other block properties will be included as well.`,
},
Options: []cmdkit.Option{
cmdkit.BoolOption("messages", "m", "show messages in block"),
cmdkit.BoolOption("receipts", "r", "show receipts in block"),
},
Run: func(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
cid, err := cid.Decode(req.Arguments[0])
Expand Down Expand Up @@ -65,14 +64,12 @@ Miner: %s
Weight: %s
Height: %s
Messages: %s
Receipts: %s
Timestamp: %s
`,
block.Header.Miner,
wStr,
strconv.FormatUint(uint64(block.Header.Height), 10),
block.Header.Messages.String(),
block.Header.MessageReceipts.String(),
strconv.FormatUint(uint64(block.Header.Timestamp), 10),
)
if err != nil {
Expand All @@ -83,13 +80,6 @@ Timestamp: %s
if showMessages == true {
_, err = fmt.Fprintf(w, `Messages: %s`+"\n", block.Messages)
}
if err != nil {
return err
}
showReceipts, _ := req.Options["receipts"].(bool)
if showReceipts == true {
_, err = fmt.Fprintf(w, `Receipts: %s`+"\n", block.Receipts)
}
return err
}),
},
Expand Down
10 changes: 1 addition & 9 deletions cmd/go-filecoin/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func TestBlockDaemon(t *testing.T) {
assert.Equal(t, 0, len(receipts))
})

t.Run("show messages and show receipts", func(t *testing.T) {
t.Run("show messages", func(t *testing.T) {
d := th.NewDaemon(
t,
th.DefaultAddress(fixtures.TestAddresses[0]),
Expand Down Expand Up @@ -170,23 +170,15 @@ func TestBlockDaemon(t *testing.T) {
require.NoError(t, json.Unmarshal([]byte(blockGetLine), &blockGetBlock))

assert.Equal(t, 3, len(blockGetBlock.Messages))
assert.Equal(t, 3, len(blockGetBlock.Receipts))

fromAddr, err := address.NewFromString(from)
require.NoError(t, err)
assert.Equal(t, fromAddr, blockGetBlock.Messages[0].Message.From)
assert.Equal(t, uint8(0), blockGetBlock.Receipts[0].ExitCode)

// Full block matches show messages
messagesGetLine := th.RunSuccessFirstLine(d, "show", "messages", blockGetBlock.Header.Messages.SecpRoot.String(), "--enc", "json")
var messages []*types.SignedMessage
require.NoError(t, json.Unmarshal([]byte(messagesGetLine), &messages))
assert.Equal(t, blockGetBlock.Messages, messages)

// Full block matches show receipts
receiptsGetLine := th.RunSuccessFirstLine(d, "show", "receipts", blockGetBlock.Header.MessageReceipts.String(), "--enc", "json")
var receipts []*types.MessageReceipt
require.NoError(t, json.Unmarshal([]byte(receiptsGetLine), &receipts))
assert.Equal(t, blockGetBlock.Receipts, receipts)
})
}
11 changes: 6 additions & 5 deletions internal/app/go-filecoin/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -704,11 +704,12 @@ func (node *Node) CreateMiningWorker(ctx context.Context) (mining.Worker, error)
MinerOwnerAddr: minerOwnerAddr,
WorkerSigner: node.Wallet.Wallet,

GetStateTree: node.getStateTree,
GetWeight: node.getWeight,
GetAncestors: node.getAncestors,
Election: consensus.ElectionMachine{},
TicketGen: consensus.TicketMachine{},
GetStateTree: node.getStateTree,
GetWeight: node.getWeight,
GetAncestors: node.getAncestors,
Election: consensus.ElectionMachine{},
TicketGen: consensus.TicketMachine{},
TipSetMetadata: node.chain.ChainReader,

MessageSource: node.Messaging.Inbox.Pool(),
MessageStore: node.chain.MessageStore,
Expand Down
6 changes: 0 additions & 6 deletions internal/app/go-filecoin/porcelain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ func ChainHead(plumbing chainHeadPlumbing) (block.TipSet, error) {
type fullBlockPlumbing interface {
ChainGetBlock(context.Context, cid.Cid) (*block.Block, error)
ChainGetMessages(context.Context, types.TxMeta) ([]*types.SignedMessage, error)
ChainGetReceipts(context.Context, cid.Cid) ([]*types.MessageReceipt, error)
}

// GetFullBlock returns a full block: header, messages, receipts.
Expand All @@ -40,10 +39,5 @@ func GetFullBlock(ctx context.Context, plumbing fullBlockPlumbing, id cid.Cid) (
return nil, err
}

out.Receipts, err = plumbing.ChainGetReceipts(ctx, out.Header.MessageReceipts)
if err != nil {
return nil, err
}

return &out, nil
}
4 changes: 1 addition & 3 deletions internal/pkg/block/full_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,12 @@ import "github.com/filecoin-project/go-filecoin/internal/pkg/types"
type FullBlock struct {
Header *Block
Messages []*types.SignedMessage
Receipts []*types.MessageReceipt
}

// NewFullBlock constructs a new full block.
func NewFullBlock(header *Block, msgs []*types.SignedMessage, rcpts []*types.MessageReceipt) *FullBlock {
func NewFullBlock(header *Block, msgs []*types.SignedMessage) *FullBlock {
return &FullBlock{
Header: header,
Messages: msgs,
Receipts: rcpts,
}
}
2 changes: 1 addition & 1 deletion internal/pkg/chain/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ type FakeStateEvaluator struct {
}

// RunStateTransition delegates to StateBuilder.ComputeState.
func (e *FakeStateEvaluator) RunStateTransition(ctx context.Context, tip block.TipSet, blsMessages [][]*types.UnsignedMessage, secpMessages [][]*types.SignedMessage, ancestors []block.TipSet, parentWeight uint64, stateID cid.Cid) (cid.Cid, []*types.MessageReceipt, error) {
func (e *FakeStateEvaluator) RunStateTransition(ctx context.Context, tip block.TipSet, blsMessages [][]*types.UnsignedMessage, secpMessages [][]*types.SignedMessage, ancestors []block.TipSet, parentWeight uint64, stateID cid.Cid, receiptCid cid.Cid) (cid.Cid, []*types.MessageReceipt, error) {
return e.ComputeState(stateID, blsMessages, secpMessages)
}

Expand Down
12 changes: 9 additions & 3 deletions internal/pkg/chainsync/internal/syncer/syncer.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ type ChainReaderWriter interface {
GetHead() block.TipSetKey
GetTipSet(tsKey block.TipSetKey) (block.TipSet, error)
GetTipSetStateRoot(tsKey block.TipSetKey) (cid.Cid, error)
GetTipSetReceiptsRoot(tsKey block.TipSetKey) (cid.Cid, error)
HasTipSetAndState(ctx context.Context, tsKey block.TipSetKey) bool
PutTipSetMetadata(ctx context.Context, tsas *chain.TipSetMetadata) error
SetHead(ctx context.Context, s block.TipSet) error
Expand Down Expand Up @@ -116,7 +117,7 @@ type HeaderValidator interface {
type FullBlockValidator interface {
// RunStateTransition returns the state root CID resulting from applying the input ts to the
// prior `stateRoot`. It returns an error if the transition is invalid.
RunStateTransition(ctx context.Context, ts block.TipSet, blsMessages [][]*types.UnsignedMessage, secpMessages [][]*types.SignedMessage, ancestors []block.TipSet, parentWeight uint64, stateID cid.Cid) (cid.Cid, []*types.MessageReceipt, error)
RunStateTransition(ctx context.Context, ts block.TipSet, blsMessages [][]*types.UnsignedMessage, secpMessages [][]*types.SignedMessage, ancestors []block.TipSet, parentWeight uint64, stateID cid.Cid, receiptRoot cid.Cid) (cid.Cid, []*types.MessageReceipt, error)
}

var reorgCnt *metrics.Int64Counter
Expand Down Expand Up @@ -213,7 +214,7 @@ func (syncer *Syncer) syncOne(ctx context.Context, grandParent, parent, next blo
stopwatch := syncOneTimer.Start(ctx)
defer stopwatch.Stop(ctx)

// Lookup parent state root. It is guaranteed by the syncer that it is in the chainStore.
// Lookup parent state and receipt root. It is guaranteed by the syncer that it is in the chainStore.
stateRoot, err := syncer.chainStore.GetTipSetStateRoot(parent.Key())
if err != nil {
return err
Expand Down Expand Up @@ -250,9 +251,14 @@ func (syncer *Syncer) syncOne(ctx context.Context, grandParent, parent, next blo
return err
}

parentReceiptRoot, err := syncer.chainStore.GetTipSetReceiptsRoot(parent.Key())
if err != nil {
return err
}

// Run a state transition to validate the tipset and compute
// a new state to add to the store.
root, receipts, err := syncer.fullValidator.RunStateTransition(ctx, next, nextBlsMessages, nextSecpMessages, ancestors, parentWeight, stateRoot)
root, receipts, err := syncer.fullValidator.RunStateTransition(ctx, next, nextBlsMessages, nextSecpMessages, ancestors, parentWeight, stateRoot, parentReceiptRoot)
if err != nil {
return err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ type integrationStateEvaluator struct {
c512 cid.Cid
}

func (n *integrationStateEvaluator) RunStateTransition(_ context.Context, ts block.TipSet, _ [][]*types.UnsignedMessage, _ [][]*types.SignedMessage, _ []block.TipSet, _ uint64, stateID cid.Cid) (cid.Cid, []*types.MessageReceipt, error) {
func (n *integrationStateEvaluator) RunStateTransition(_ context.Context, ts block.TipSet, _ [][]*types.UnsignedMessage, _ [][]*types.SignedMessage, _ []block.TipSet, _ uint64, stateID cid.Cid, rCid cid.Cid) (cid.Cid, []*types.MessageReceipt, error) {
for i := 0; i < ts.Len(); i++ {
if ts.At(i).StateRoot.Equals(n.c512) {
return n.c512, []*types.MessageReceipt{}, nil
Expand Down
2 changes: 1 addition & 1 deletion internal/pkg/chainsync/internal/syncer/syncer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ func newPoisonValidator(t *testing.T, headerFailure, fullFailure uint64) *poison
return &poisonValidator{headerFailureTS: headerFailure, fullFailureTS: fullFailure}
}

func (pv *poisonValidator) RunStateTransition(_ context.Context, ts block.TipSet, _ [][]*types.UnsignedMessage, _ [][]*types.SignedMessage, _ []block.TipSet, _ uint64, _ cid.Cid) (cid.Cid, []*types.MessageReceipt, error) {
func (pv *poisonValidator) RunStateTransition(_ context.Context, ts block.TipSet, _ [][]*types.UnsignedMessage, _ [][]*types.SignedMessage, _ []block.TipSet, _ uint64, _ cid.Cid, _ cid.Cid) (cid.Cid, []*types.MessageReceipt, error) {
stamp, err := ts.MinTimestamp()
require.NoError(pv.t, err)

Expand Down
105 changes: 45 additions & 60 deletions internal/pkg/consensus/expected.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ func init() {
var (
// ErrStateRootMismatch is returned when the computed state root doesn't match the expected result.
ErrStateRootMismatch = errors.New("blocks state root does not match computed result")
// ErrInvalidBase is returned when the chain doesn't connect back to a known good block.
ErrInvalidBase = errors.New("block does not connect to a known good chain")
// ErrUnorderedTipSets is returned when weight and minticket are the same between two tipsets.
ErrUnorderedTipSets = errors.New("trying to order two identical tipsets")
// ErrReceiptRootMismatch is returned when the block's receipt root doesn't match the receipt root computed for the parent tipset.
ErrReceiptRootMismatch = errors.New("blocks receipt root does not match parent tip set")
)

// DefaultBlockTime is the estimated proving period time.
Expand All @@ -66,9 +70,6 @@ var AncestorRoundsNeeded = max(miner.LargestSectorSizeProvingPeriodBlocks+miner.

// A Processor processes all the messages in a block or tip set.
type Processor interface {
// ProcessBlock processes all messages in a block.
ProcessBlock(context.Context, state.Tree, vm.StorageMap, *block.Block, []*types.UnsignedMessage, []block.TipSet) ([]*ApplicationResult, error)

// ProcessTipSet processes all messages in a tip set.
ProcessTipSet(context.Context, state.Tree, vm.StorageMap, block.TipSet, [][]*types.UnsignedMessage, []block.TipSet) (*ProcessTipSetResponse, error)
}
Expand Down Expand Up @@ -137,17 +138,17 @@ func (c *Expected) BlockTime() time.Duration {
// RunStateTransition applies the messages in a tipset to a state, and persists that new state.
// It errors if the tipset was not mined according to the EC rules, or if any of the messages
// in the tipset results in an error.
func (c *Expected) RunStateTransition(ctx context.Context, ts block.TipSet, blsMessages [][]*types.UnsignedMessage, secpMessages [][]*types.SignedMessage, ancestors []block.TipSet, parentWeight uint64, priorStateID cid.Cid) (root cid.Cid, receipts []*types.MessageReceipt, err error) {
func (c *Expected) RunStateTransition(ctx context.Context, ts block.TipSet, blsMessages [][]*types.UnsignedMessage, secpMessages [][]*types.SignedMessage, ancestors []block.TipSet, parentWeight uint64, parentStateRoot cid.Cid, parentReceiptRoot cid.Cid) (root cid.Cid, receipts []*types.MessageReceipt, err error) {
ctx, span := trace.StartSpan(ctx, "Expected.RunStateTransition")
span.AddAttributes(trace.StringAttribute("tipset", ts.String()))
defer tracing.AddErrorEndSpan(ctx, span, &err)

priorState, err := c.loadStateTree(ctx, priorStateID)
priorState, err := c.loadStateTree(ctx, parentStateRoot)
if err != nil {
return cid.Undef, []*types.MessageReceipt{}, err
}

if err := c.validateMining(ctx, priorState, ts, ancestors[0], ancestors, blsMessages, secpMessages, parentWeight); err != nil {
if err := c.validateMining(ctx, priorState, ts, ancestors[0], ancestors, blsMessages, secpMessages, parentWeight, parentStateRoot, parentReceiptRoot); err != nil {
return cid.Undef, []*types.MessageReceipt{}, err
}

Expand Down Expand Up @@ -179,7 +180,18 @@ func (c *Expected) RunStateTransition(ctx context.Context, ts block.TipSet, blsM
// * has a losing election proof
// Returns nil if all the above checks pass.
// See https://github.com/filecoin-project/specs/blob/master/mining.md#chain-validation
func (c *Expected) validateMining(ctx context.Context, st state.Tree, ts block.TipSet, parentTs block.TipSet, ancestors []block.TipSet, blsMsgs [][]*types.UnsignedMessage, secpMsgs [][]*types.SignedMessage, parentWeight uint64) error {
func (c *Expected) validateMining(
ctx context.Context,
st state.Tree,
ts block.TipSet,
parentTs block.TipSet,
ancestors []block.TipSet,
blsMsgs [][]*types.UnsignedMessage,
secpMsgs [][]*types.SignedMessage,
parentWeight uint64,
parentStateRoot cid.Cid,
parentReceiptRoot cid.Cid) error {

electionTicket, err := sampling.SampleNthTicket(ElectionLookback-1, ancestors)
if err != nil {
return errors.Wrap(err, "failed to sample election ticket from ancestors")
Expand All @@ -198,6 +210,16 @@ func (c *Expected) validateMining(ctx context.Context, st state.Tree, ts block.T
for i := 0; i < ts.Len(); i++ {
blk := ts.At(i)

// confirm block state root matches parent state root
if !parentStateRoot.Equals(blk.StateRoot) {
return ErrStateRootMismatch
}

// confirm block receipts match parent receipts
if !parentReceiptRoot.Equals(blk.MessageReceipts) {
return ErrReceiptRootMismatch
}

if uint64(blk.ParentWeight) != parentWeight {
return errors.Errorf("block %s has invalid parent weight %d", blk.Cid().String(), parentWeight)
}
Expand Down Expand Up @@ -241,67 +263,20 @@ func (c *Expected) validateMining(ctx context.Context, st state.Tree, ts block.T
}

// runMessages applies the messages of all blocks within the input
// tipset to the input base state. Messages are applied block by
// block with blocks sorted by their ticket bytes. The output state must be
// flushed after calling to guarantee that the state transitions propagate.
//
// An error is returned if individual blocks contain messages that do not
// lead to successful state transitions. An error is also returned if the node
// faults while running aggregate state computation.
// tipset to the input base state. Messages are extracted from tipset
// blocks sorted by their ticket bytes and run as a single state transition
// for the entire tipset. The output state must be flushed after calling to
// guarantee that the state transitions propagate.
func (c *Expected) runMessages(ctx context.Context, st state.Tree, vms vm.StorageMap, ts block.TipSet, blsMessages [][]*types.UnsignedMessage, secpMessages [][]*types.UnsignedMessage, ancestors []block.TipSet) (state.Tree, []*types.MessageReceipt, error) {
var cpySt state.Tree
var results []*ApplicationResult
allMessages := combineMessages(blsMessages, secpMessages)

// TODO: don't process messages twice
for i := 0; i < ts.Len(); i++ {
blk := ts.At(i)
cpyCid, err := st.Flush(ctx)
if err != nil {
return nil, nil, errors.Wrap(err, "error validating block state")
}
// state copied so changes don't propagate between block validations
cpySt, err = c.loadStateTree(ctx, cpyCid)
if err != nil {
return nil, nil, errors.Wrap(err, "error validating block state")
}

// Combine messages to process BLS first.
msgs := append(blsMessages[i], secpMessages[i]...)
results, err = c.processor.ProcessBlock(ctx, cpySt, vms, blk, msgs, ancestors)
if err != nil {
return nil, nil, errors.Wrap(err, "error validating block state")
}

outCid, err := cpySt.Flush(ctx)
if err != nil {
return nil, nil, errors.Wrap(err, "error validating block state")
}

if !outCid.Equals(blk.StateRoot) {
return nil, nil, ErrStateRootMismatch
}
}

if ts.Len() <= 1 { // block validation state == aggregate parent state
receipts := make([]*types.MessageReceipt, len(results))
for i, res := range results {
receipts[i] = res.Receipt
}
return cpySt, receipts, nil
}

// multiblock tipsets require reapplying messages to get aggregate state
// NOTE: It is possible to optimize further by applying block validation
// in sorted order to reuse first block transitions as the starting state
// for the tipSetProcessor.
allMessages := append(blsMessages, secpMessages...)
resp, err := c.processor.ProcessTipSet(ctx, st, vms, ts, allMessages, ancestors)
if err != nil {
return nil, nil, errors.Wrap(err, "error validating tipset")
}

receipts := make([]*types.MessageReceipt, len(resp.Results))
for i, res := range results {
for i, res := range resp.Results {
receipts[i] = res.Receipt
}

Expand Down Expand Up @@ -335,6 +310,16 @@ func verifyBLSMessageAggregate(sig types.Signature, msgs []*types.UnsignedMessag
return nil
}

// combineMessages takes lists of bls and secp messages grouped by block and combines them so
// that they are still grouped by block and the bls messages come before secp messages in the same block.
func combineMessages(blsMsgs [][]*types.UnsignedMessage, secpMsgs [][]*types.UnsignedMessage) [][]*types.UnsignedMessage {
combined := make([][]*types.UnsignedMessage, len(blsMsgs))
for blkIndex := 0; blkIndex < len(blsMsgs); blkIndex++ {
combined[blkIndex] = append(blsMsgs[blkIndex], secpMsgs[blkIndex]...)
}
return combined
}

// Unwraps nested slices of signed messages.
// Much better than this function would be a type encapsulating a tipset's messages, along
// with deduplication and unwrapping logic.
Expand Down
Loading

0 comments on commit c6b771c

Please sign in to comment.