Skip to content

Commit

Permalink
feat: implement TipSetBlockMessagesReceipts method on ChainStore
Browse files Browse the repository at this point in the history
- TipSetBlockMessagesReceipts returns the blocks and messages in a tipset and their corresponding
receipts from its parent tipset matching block order in tipset.
  • Loading branch information
frrist committed Aug 19, 2022
1 parent b5ac141 commit 4541329
Showing 1 changed file with 142 additions and 0 deletions.
142 changes: 142 additions & 0 deletions chain/store/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package store

import (
"context"
"fmt"

block "github.com/ipfs/go-block-format"
"github.com/ipfs/go-cid"
Expand Down Expand Up @@ -300,3 +301,144 @@ func (cs *ChainStore) LoadSignedMessagesFromCids(ctx context.Context, cids []cid

return msgs, nil
}

// TipSetBlockMessagesReceipts returns the blocks and messages in `ts` and their corresponding receipts from `pts` matching block order in tipset (`ts`).
func (cs *ChainStore) TipSetBlockMessagesReceipts(ctx context.Context, ts, pts *types.TipSet) ([]*BlockMessageReceipts, error) {
// returned BlockMessages match block order in tipset
blkMsgs, err := cs.BlockMsgsForTipset(ctx, pts)
if err != nil {
return nil, err
}
if len(blkMsgs) != len(pts.Blocks()) {
// logic error somewhere
return nil, fmt.Errorf("mismatching number of blocks returned from block messages, got %d wanted %d", len(blkMsgs), len(pts.Blocks()))
}

// retrieve receipts using a block from the child (ts) tipset
// TODO this operation can fail when the node is imported from a snapshot that does not contain receipts (most don't)
// the solution is to compute the tipset state which will create the receipts we load here
rs, err := blockadt.AsArray(cs.ActorStore(ctx), ts.Blocks()[0].ParentMessageReceipts)
if err != nil {
return nil, fmt.Errorf("loading message receipts %w", err)
}
// so we only load the receipt array one
getReceipt := func(idx int) (*types.MessageReceipt, error) {
var r types.MessageReceipt
if found, err := rs.Get(uint64(idx), &r); err != nil {
return nil, err
} else if !found {
return nil, fmt.Errorf("failed to find receipt %d", idx)
}
return &r, nil
}

out := make([]*BlockMessageReceipts, len(pts.Blocks()))
executionIndex := 0
// walk each block in tipset, `pts.Blocks()` has same ordering as `blkMsgs`.
for blkIdx := range pts.Blocks() {
// bls and secp messages for block
msgs := blkMsgs[blkIdx]
// index of messages in `out.Messages`
msgIdx := 0
// index or receipts in `out.Receipts`
receiptIdx := 0
out[blkIdx] = &BlockMessageReceipts{
// block containing messages
Block: pts.Blocks()[blkIdx],
// total messages returned equal to sum of bls and secp messages
Messages: make([]types.ChainMsg, len(msgs.BlsMessages)+len(msgs.SecpkMessages)),
// total receipts returned equal to sum of bls and secp messages
Receipts: make([]*types.MessageReceipt, len(msgs.BlsMessages)+len(msgs.SecpkMessages)),
// index of message indicating execution order.
MessageExecutionIndex: make(map[types.ChainMsg]int),
}
// walk bls messages and extract their receipts
for blsIdx := range msgs.BlsMessages {
// location in receipt array corresponds to message execution order across all blocks
receipt, err := getReceipt(executionIndex)
if err != nil {
return nil, err
}
out[blkIdx].Messages[msgIdx] = msgs.BlsMessages[blsIdx]
out[blkIdx].Receipts[receiptIdx] = receipt
out[blkIdx].MessageExecutionIndex[msgs.BlsMessages[blsIdx]] = executionIndex
msgIdx++
receiptIdx++
executionIndex++
}
// walk secp messages and extract their receipts
for secpIdx := range msgs.SecpkMessages {
// location in receipt array corresponds to message execution order across all blocks
receipt, err := getReceipt(executionIndex)
if err != nil {
return nil, err
}
out[blkIdx].Messages[msgIdx] = msgs.SecpkMessages[secpIdx]
out[blkIdx].Receipts[receiptIdx] = receipt
out[blkIdx].MessageExecutionIndex[msgs.SecpkMessages[secpIdx]] = executionIndex
msgIdx++
receiptIdx++
executionIndex++
}
}
return out, nil
}

// BlockMessageReceipts contains a block its messages and their corresponding receipts.
// The Receipts are one-to-one with Messages index.
type BlockMessageReceipts struct {
Block *types.BlockHeader
// Messages contained in Block.
Messages []types.ChainMsg
// Receipts contained in Block.
Receipts []*types.MessageReceipt
// MessageExectionIndex contains a mapping of Messages to their execution order in the tipset they were included.
MessageExecutionIndex map[types.ChainMsg]int
}

type MessageReceiptIterator struct {
// index in msgs and receipts.
idx int
msgs []types.ChainMsg
receipts []*types.MessageReceipt
// maps msgs to their execution order in tipset application.
exeIdx map[types.ChainMsg]int
}

// Iterator returns a MessageReceiptIterator to conveniently iterate messages, their execution index, and their respective receipts.
func (bmr *BlockMessageReceipts) Iterator() (*MessageReceiptIterator, error) {
if len(bmr.Messages) != len(bmr.Receipts) {
return nil, fmt.Errorf("invalid construction, expected equal number receipts (%d) and messages (%d)", len(bmr.Receipts), len(bmr.Messages))
}
return &MessageReceiptIterator{
idx: 0,
msgs: bmr.Messages,
receipts: bmr.Receipts,
exeIdx: bmr.MessageExecutionIndex,
}, nil
}

// HasNext returns `true` while there are messages/receipts to iterate.
func (mri *MessageReceiptIterator) HasNext() bool {
if mri.idx < len(mri.msgs) {
return true
}
return false
}

// Next returns the next message, execution index, and receipt in the MessageReceiptIterator.
func (mri *MessageReceiptIterator) Next() (types.ChainMsg, int, *types.MessageReceipt) {
if mri.HasNext() {
msg := mri.msgs[mri.idx]
exeIdx := mri.exeIdx[msg]
rec := mri.receipts[mri.idx]
mri.idx++
return msg, exeIdx, rec
}
return nil, -1, nil
}

// Reset resets the MessageReceiptIterator to the first message/receipt.
func (mri *MessageReceiptIterator) Reset() {
mri.idx = 0
}

0 comments on commit 4541329

Please sign in to comment.