|
| 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