Skip to content

Commit fc5d4b1

Browse files
committed
fixup! feat(exec): dump intermediate cache blocks from FVM exec in StateReplay
1 parent 369d5be commit fc5d4b1

File tree

2 files changed

+394
-22
lines changed

2 files changed

+394
-22
lines changed

cmd/lotus-shed/block-matcher.go

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
8+
blocks "github.com/ipfs/go-block-format"
9+
cbor "github.com/ipfs/go-ipld-cbor"
10+
"github.com/ipld/go-ipld-prime"
11+
"github.com/ipld/go-ipld-prime/codec/dagcbor"
12+
"github.com/ipld/go-ipld-prime/datamodel"
13+
"github.com/ipld/go-ipld-prime/node/bindnode"
14+
"github.com/ipld/go-ipld-prime/schema"
15+
schemadmt "github.com/ipld/go-ipld-prime/schema/dmt"
16+
schemadsl "github.com/ipld/go-ipld-prime/schema/dsl"
17+
"github.com/ipld/go-ipld-prime/traversal"
18+
cbg "github.com/whyrusleeping/cbor-gen"
19+
"golang.org/x/xerrors"
20+
21+
"github.com/filecoin-project/go-amt-ipld/v4"
22+
"github.com/filecoin-project/go-bitfield"
23+
"github.com/filecoin-project/go-hamt-ipld/v3"
24+
gstbuiltin "github.com/filecoin-project/go-state-types/builtin"
25+
datacap16 "github.com/filecoin-project/go-state-types/builtin/v16/datacap"
26+
market16 "github.com/filecoin-project/go-state-types/builtin/v16/market"
27+
miner16 "github.com/filecoin-project/go-state-types/builtin/v16/miner"
28+
power16 "github.com/filecoin-project/go-state-types/builtin/v16/power"
29+
"github.com/filecoin-project/go-state-types/builtin/v16/util/adt"
30+
verifreg16 "github.com/filecoin-project/go-state-types/builtin/v16/verifreg"
31+
32+
"github.com/filecoin-project/lotus/chain/types"
33+
)
34+
35+
// matchKnownBlockType attempts to determine the type of a block by inspecting its bytes. First we
36+
// attempt to decode it as part of a HAMT or AMT, and if we get one, we inspect the types of the
37+
// values. Otherwise we attempt to decode it as a known type using matchKnownBlockTypeFromBytes.
38+
func matchKnownBlockType(ctx context.Context, nd blocks.Block) (string, error) {
39+
if m, err := matchKnownBlockTypeFromBytes(nd.RawData()); err != nil {
40+
return "", err
41+
} else if m != "" {
42+
return m, nil
43+
}
44+
45+
// block store with just one block in it, for interacting with the hamt and amt libraries
46+
store := cbor.NewMemCborStore()
47+
if err := store.(*cbor.BasicIpldStore).Blocks.Put(ctx, nd); err != nil {
48+
return "", err
49+
}
50+
51+
// try to load as a HAMT root/node (they are the same thing)
52+
if _, err := hamt.LoadNode(ctx, store, nd.Cid(), append(adt.DefaultHamtOptions, hamt.UseTreeBitWidth(gstbuiltin.DefaultHamtBitwidth))...); err == nil {
53+
// got a HAMT, now inspect it
54+
hamtNode, err := ipld.DecodeUsingPrototype(nd.RawData(), dagcbor.Decode, bindnode.Prototype(nil, knownTypeSystem.TypeByName("HamtNode")))
55+
if err != nil {
56+
return "", xerrors.Errorf("failed to decode HamtNode: %w", err)
57+
}
58+
typ, err := matchHamtValues(hamtNode)
59+
if err != nil {
60+
return "", err
61+
}
62+
return fmt.Sprintf("HAMTNode{%d}%s", gstbuiltin.DefaultHamtBitwidth, typ), nil
63+
}
64+
65+
// try to load as an AMT root, we have to try all bitwidths used in the chain
66+
for _, bitwidth := range []uint{2, 3, 4, 5, 6} {
67+
if _, err := amt.LoadAMT(ctx, store, nd.Cid(), append(adt.DefaultAmtOptions, amt.UseTreeBitWidth(bitwidth))...); err == nil {
68+
// got an AMT root, now inspect it
69+
amtRoot, err := ipld.DecodeUsingPrototype(nd.RawData(), dagcbor.Decode, bindnode.Prototype(nil, knownTypeSystem.TypeByName("AMTRoot")))
70+
if err != nil {
71+
return "", xerrors.Errorf("failed to decode AMTRoot: %w", err)
72+
}
73+
values, err := traversal.Get(amtRoot, datamodel.ParsePath("Node/Values"))
74+
if err != nil {
75+
return "", xerrors.Errorf("failed to get AMTRoot.Node.Values: %w", err)
76+
}
77+
typ, err := matchAmtValues(values)
78+
if err != nil {
79+
return "", err
80+
}
81+
return fmt.Sprintf("AMTRoot{%d}%s", bitwidth, typ), nil
82+
}
83+
}
84+
85+
// try to load as an AMT intermediate node, which we can't do using the amt package so we'll
86+
// infer by schema
87+
if amtNode, err := ipld.DecodeUsingPrototype(nd.RawData(), dagcbor.Decode, bindnode.Prototype(nil, knownTypeSystem.TypeByName("AMTNode"))); err == nil {
88+
// got an AMT node, now inspect it
89+
values, err := amtNode.LookupByString("Values")
90+
if err != nil {
91+
return "", xerrors.Errorf("failed to get AMTNode.Values: %w", err)
92+
}
93+
typ, err := matchAmtValues(values)
94+
if err != nil {
95+
return "", err
96+
}
97+
return "AMTNode" + typ, nil
98+
}
99+
100+
return "", nil
101+
}
102+
103+
// given a datamodel.Node form of the Values array within an AMT node, attempt to determine the
104+
// type of the values by iterating through them all and checking from their bytes.
105+
func matchAmtValues(values datamodel.Node) (string, error) {
106+
var match string
107+
itr := values.ListIterator()
108+
for !itr.Done() {
109+
_, v, err := itr.Next()
110+
if err != nil {
111+
return "", err
112+
}
113+
enc, err := ipld.Encode(v, dagcbor.Encode)
114+
if err != nil {
115+
return "", err
116+
}
117+
if m, _ := matchKnownBlockTypeFromBytes(enc); m != "" {
118+
if match == "" {
119+
match = m
120+
} else if match != m {
121+
return "", xerrors.Errorf("inconsistent types in AMT values")
122+
}
123+
}
124+
}
125+
if match != "" {
126+
return "[" + match + "]", nil
127+
}
128+
return "", nil
129+
}
130+
131+
// given a datamodel.Node form of a HAMT node, attempt to determine the type of the values, if there
132+
// are any, by iterating through them all and checking from their bytes.
133+
func matchHamtValues(hamtNode datamodel.Node) (string, error) {
134+
pointers, err := hamtNode.LookupByString("Pointers")
135+
if err != nil {
136+
return "", xerrors.Errorf("failed to get HamtNode.Pointers: %w", err)
137+
}
138+
var match string
139+
itr := pointers.ListIterator()
140+
for !itr.Done() {
141+
_, v, err := itr.Next()
142+
if err != nil {
143+
return "", err
144+
}
145+
b, err := v.LookupByString("Bucket")
146+
if err == nil {
147+
bitr := b.ListIterator()
148+
for !bitr.Done() {
149+
_, kv, err := bitr.Next()
150+
if err != nil {
151+
return "", err
152+
}
153+
bval, err := kv.LookupByString("Value")
154+
if err != nil {
155+
return "", err
156+
}
157+
enc, err := ipld.Encode(bval, dagcbor.Encode)
158+
if err != nil {
159+
return "", err
160+
}
161+
if m, _ := matchKnownBlockTypeFromBytes(enc); m != "" {
162+
if match == "" {
163+
match = m
164+
} else if match != m {
165+
return "", xerrors.Errorf("inconsistent types in HAMT values")
166+
}
167+
}
168+
}
169+
}
170+
}
171+
if match != "" {
172+
return "[" + match + "]", nil
173+
}
174+
return "", nil
175+
}
176+
177+
var wellKnownBlockBytes = map[string][]byte{
178+
"EmptyArray": {0x80},
179+
"EmptyBytes": {0x40},
180+
"EmptyString": {0x60}, // is this used anywhere in the chain?
181+
"Zero": {0x00}, // is this used anywhere in the chain?
182+
"HAMTNode{5}[empty]": {0x82, 0x40, 0x80},
183+
"AMTRoot{2/3}[empty]": {0x84, 0x02, 0x00, 0x00, 0x83, 0x41, 0x00, 0x80, 0x80},
184+
"AMTRoot{4}[empty]": {0x84, 0x04, 0x00, 0x00, 0x83, 0x42, 0x00, 0x00, 0x80, 0x80},
185+
"AMTRoot{5}[empty]": {0x84, 0x05, 0x00, 0x00, 0x83, 0x44, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80},
186+
"AMTRoot{6}[empty]": {0x84, 0x06, 0x00, 0x00, 0x83, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80},
187+
}
188+
189+
func matchWellKnownBlockType(b []byte) (string, error) {
190+
for name, wkb := range wellKnownBlockBytes {
191+
if bytes.Equal(b, wkb) {
192+
return name, nil
193+
}
194+
}
195+
return "", nil
196+
}
197+
198+
// matchKnownBlockTypeFromBytes attempts to determine the type of a block by inspecting its bytes.
199+
// We use a fixed list of known types that have a CBORUnmarshaler that we believe may be possible.
200+
// This list is not exhaustive and should be expanded as unknown types are encountered.
201+
func matchKnownBlockTypeFromBytes(b []byte) (string, error) {
202+
if m, _ := matchWellKnownBlockType(b); m != "" {
203+
return m, nil
204+
}
205+
206+
if _, err := cbg.ReadCid(bytes.NewReader(b)); err == nil {
207+
return "Cid", nil
208+
}
209+
known := map[string]cbg.CBORUnmarshaler{
210+
// Fill this out with known types when you see them missing and can identify them
211+
"BlockHeader": &types.BlockHeader{},
212+
"miner16.State": &miner16.State{},
213+
"miner16.MinerInfo": &miner16.MinerInfo{},
214+
"miner16.Deadlines": &miner16.Deadlines{},
215+
"miner16.Deadline": &miner16.Deadline{},
216+
"miner16.Partition": &miner16.Partition{},
217+
"miner16.ExpirationSet": &miner16.ExpirationSet{},
218+
"miner16.WindowedPoSt": &miner16.WindowedPoSt{},
219+
"miner16.SectorOnChainInfo": &miner16.SectorOnChainInfo{},
220+
"miner16.SectorPreCommitOnChainInfo": &miner16.SectorPreCommitOnChainInfo{},
221+
"power16.State": &power16.State{},
222+
"market16.State": &market16.State{},
223+
"verifreg16.State": &verifreg16.State{},
224+
"datacap16.State": &datacap16.State{},
225+
"Bitfield": &bitfield.BitField{},
226+
}
227+
for name, v := range known {
228+
if err := v.UnmarshalCBOR(bytes.NewReader(b)); err == nil {
229+
return name, nil
230+
}
231+
}
232+
return "", nil
233+
}
234+
235+
const knownTypesSchema = `
236+
type HamtNode struct {
237+
Bitfield Bytes
238+
Pointers [Pointer]
239+
} representation tuple
240+
241+
type Pointer union {
242+
| Any link # link to HamtNode
243+
| Bucket list
244+
} representation kinded
245+
246+
type Bucket [KV]
247+
248+
type KV struct {
249+
Key Bytes
250+
Value Any
251+
} representation tuple
252+
253+
type AMTNode struct {
254+
Bmap Bytes
255+
Links [Link]
256+
Values [Any]
257+
} representation tuple
258+
259+
type AMTRoot struct {
260+
BitWidth Int
261+
Height Int
262+
Count Int
263+
Node AMTNode
264+
} representation tuple
265+
`
266+
267+
var knownTypeSystem schema.TypeSystem
268+
269+
func init() {
270+
sch, err := schemadsl.ParseBytes([]byte(knownTypesSchema))
271+
if err != nil {
272+
panic(err)
273+
}
274+
knownTypeSystem.Init()
275+
if err := schemadmt.Compile(&knownTypeSystem, sch); err != nil {
276+
panic(err)
277+
}
278+
}

0 commit comments

Comments
 (0)