diff --git a/.gitmodules b/.gitmodules index a655f05b9..709a28003 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = extern/filecoin-ffi url = https://github.com/filecoin-project/filecoin-ffi.git branch = master +[submodule "extern/serialization-vectors"] + path = extern/serialization-vectors + url = https://github.com/filecoin-project/serialization-vectors diff --git a/build/params_shared.go b/build/params_shared.go index e1ac74110..5fcc73181 100644 --- a/build/params_shared.go +++ b/build/params_shared.go @@ -64,7 +64,7 @@ const MaxSealLookback = SealRandomnessLookbackLimit + 2000 // Mining // Epochs -const EcRandomnessLookback = 300 +const EcRandomnessLookback = 1 // ///// // Devnet settings diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 1a0ae8ca0..9fc9a79c6 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -3,8 +3,8 @@ package gen import ( "bytes" "context" - "crypto/sha256" "fmt" + "github.com/minio/blake2b-simd" "io/ioutil" "sync/atomic" @@ -276,8 +276,12 @@ func CarWalkFunc(nd format.Node) (out []*format.Link, err error) { func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m address.Address, round int64) (*types.EPostProof, *types.Ticket, error) { mc := &mca{w: cg.w, sm: cg.sm} - // TODO: REVIEW: Am I doing this correctly? - ticketRand, err := mc.ChainGetRandomness(ctx, pts.Key(), crypto.DomainSeparationTag_TicketProduction, pts.Height(), m.Bytes()) + buf := new(bytes.Buffer) + if err := m.MarshalCBOR(buf); err != nil { + return nil, nil, xerrors.Errorf("failed to cbor marshal address: %w", err) + } + + ticketRand, err := mc.ChainGetRandomness(ctx, pts.Key(), crypto.DomainSeparationTag_TicketProduction, abi.ChainEpoch(round-build.EcRandomnessLookback), buf.Bytes()) if err != nil { return nil, nil, err } @@ -549,7 +553,11 @@ type ProofInput struct { } func IsRoundWinner(ctx context.Context, ts *types.TipSet, round int64, miner address.Address, epp ElectionPoStProver, a MiningCheckAPI) (*ProofInput, error) { - epostRand, err := a.ChainGetRandomness(ctx, ts.Key(), crypto.DomainSeparationTag_ElectionPoStChallengeSeed, abi.ChainEpoch(round-build.EcRandomnessLookback), miner.Bytes()) + buf := new(bytes.Buffer) + if err := miner.MarshalCBOR(buf); err != nil { + return nil, xerrors.Errorf("failed to cbor marshal address: %w") + } + epostRand, err := a.ChainGetRandomness(ctx, ts.Key(), crypto.DomainSeparationTag_ElectionPoStChallengeSeed, abi.ChainEpoch(round-build.EcRandomnessLookback), buf.Bytes()) if err != nil { return nil, xerrors.Errorf("chain get randomness: %w", err) } @@ -584,7 +592,7 @@ func IsRoundWinner(ctx context.Context, ts *types.TipSet, round int64, miner add }) } - hvrf := sha256.Sum256(vrfout) + hvrf := blake2b.Sum256(vrfout) candidates, err := epp.GenerateCandidates(ctx, sinfos, hvrf[:]) if err != nil { return nil, xerrors.Errorf("failed to generate electionPoSt candidates: %w", err) diff --git a/chain/state/statetree_test.go b/chain/state/statetree_test.go index b22828e19..a316cd063 100644 --- a/chain/state/statetree_test.go +++ b/chain/state/statetree_test.go @@ -2,12 +2,14 @@ package state import ( "context" + "fmt" "testing" "github.com/filecoin-project/specs-actors/actors/builtin" address "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/types" + "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" ) @@ -227,3 +229,45 @@ func assertNotHas(t *testing.T, st *StateTree, addr address.Address) { t.Fatal("shouldnt have found actor", addr) } } + +func TestStateTreeConsistency(t *testing.T) { + cst := cbor.NewMemCborStore() + st, err := NewStateTree(cst) + if err != nil { + t.Fatal(err) + } + + var addrs []address.Address + for i := 100; i < 150; i++ { + a, err := address.NewIDAddress(uint64(i)) + if err != nil { + t.Fatal(err) + } + + addrs = append(addrs, a) + } + + randomCid, err := cid.Decode("bafy2bzacecu7n7wbtogznrtuuvf73dsz7wasgyneqasksdblxupnyovmtwxxu") + if err != nil { + t.Fatal(err) + } + + for i, a := range addrs { + st.SetActor(a, &types.Actor{ + Code: randomCid, + Head: randomCid, + Balance: types.NewInt(uint64(10000 + i)), + Nonce: uint64(1000 - i), + }) + } + + root, err := st.Flush(context.TODO()) + if err != nil { + t.Fatal(err) + } + + fmt.Println("root is: ", root) + if root.String() != "bafy2bzacec6igwshty4qqexix6iffzdawp5e4ke7mamfn35g3ga6rc3dyhgnc" { + t.Fatal("MISMATCH!") + } +} diff --git a/chain/sync.go b/chain/sync.go index 8f72f7fdd..eeb78b745 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -1,10 +1,11 @@ package chain import ( + "bytes" "context" - "crypto/sha256" "errors" "fmt" + "github.com/minio/blake2b-simd" "sync" "time" @@ -617,7 +618,11 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err }) tktsCheck := async.Err(func() error { - vrfBase, err := syncer.sm.ChainStore().GetRandomness(ctx, baseTs.Cids(), crypto.DomainSeparationTag_TicketProduction, int64(baseTs.Height()), h.Miner.Bytes()) + buf := new(bytes.Buffer) + if err := h.Miner.MarshalCBOR(buf); err != nil { + return xerrors.Errorf("failed to marshal miner address to cbor: %w", err) + } + vrfBase, err := syncer.sm.ChainStore().GetRandomness(ctx, baseTs.Cids(), crypto.DomainSeparationTag_TicketProduction, int64(baseTs.Height()), buf.Bytes()) if err != nil { return xerrors.Errorf("failed to get randomness for verifying election proof: %w", err) } @@ -656,7 +661,11 @@ func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) err } func (syncer *Syncer) VerifyElectionPoStProof(ctx context.Context, h *types.BlockHeader, baseTs *types.TipSet, waddr address.Address) error { - rand, err := syncer.sm.ChainStore().GetRandomness(ctx, baseTs.Cids(), crypto.DomainSeparationTag_ElectionPoStChallengeSeed, int64(h.Height-build.EcRandomnessLookback), h.Miner.Bytes()) + buf := new(bytes.Buffer) + if err := h.Miner.MarshalCBOR(buf); err != nil { + return xerrors.Errorf("failed to marshal miner to cbor: %w", err) + } + rand, err := syncer.sm.ChainStore().GetRandomness(ctx, baseTs.Cids(), crypto.DomainSeparationTag_ElectionPoStChallengeSeed, int64(h.Height-build.EcRandomnessLookback), buf.Bytes()) if err != nil { return xerrors.Errorf("failed to get randomness for verifying election proof: %w", err) } @@ -725,7 +734,7 @@ func (syncer *Syncer) VerifyElectionPoStProof(ctx context.Context, h *types.Bloc // TODO: why do we need this here? challengeCount := sectorbuilder.ElectionPostChallengeCount(uint64(len(sectorInfo)), 0) - hvrf := sha256.Sum256(h.EPostProof.PostRand) + hvrf := blake2b.Sum256(h.EPostProof.PostRand) pvi := abi.PoStVerifyInfo{ Randomness: hvrf[:], Candidates: candidates, diff --git a/chain/vectors/gen/main.go b/chain/vectors/gen/main.go new file mode 100644 index 000000000..cf8ebd859 --- /dev/null +++ b/chain/vectors/gen/main.go @@ -0,0 +1,197 @@ +package main + +import ( + "encoding/json" + "fmt" + "math/rand" + "os" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/chain/gen" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/mock" + "github.com/filecoin-project/lotus/chain/vectors" + "github.com/filecoin-project/lotus/chain/wallet" + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/specs-actors/actors/abi/big" + "github.com/filecoin-project/specs-actors/actors/builtin/power" + "github.com/filecoin-project/specs-actors/actors/crypto" + + _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/secp" +) + +func init() { + power.ConsensusMinerMinPower = big.NewInt(2048) +} + +func MakeHeaderVectors() []vectors.HeaderVector { + cg, err := gen.NewGenerator() + if err != nil { + panic(err) + } + + var out []vectors.HeaderVector + for i := 0; i < 5; i++ { + nts, err := cg.NextTipSet() + if err != nil { + panic(err) + } + + h := nts.TipSet.Blocks[0].Header + data, err := h.Serialize() + if err != nil { + panic(err) + } + + out = append(out, vectors.HeaderVector{ + Block: h, + Cid: h.Cid().String(), + CborHex: fmt.Sprintf("%x", data), + }) + } + return out +} + +func MakeMessageSigningVectors() []vectors.MessageSigningVector { + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + panic(err) + } + + blsk, err := w.GenerateKey(crypto.SigTypeBLS) + if err != nil { + panic(err) + } + bki, err := w.Export(blsk) + if err != nil { + panic(err) + } + + to, err := address.NewIDAddress(99999) + if err != nil { + panic(err) + } + + bmsg := mock.MkMessage(blsk, to, 55, w) + + blsmsv := vectors.MessageSigningVector{ + Unsigned: &bmsg.Message, + Cid: bmsg.Message.Cid().String(), + CidHexBytes: fmt.Sprintf("%x", bmsg.Message.Cid().Bytes()), + PrivateKey: bki.PrivateKey, + Signature: &bmsg.Signature, + } + + secpk, err := w.GenerateKey(crypto.SigTypeBLS) + if err != nil { + panic(err) + } + ski, err := w.Export(secpk) + if err != nil { + panic(err) + } + + smsg := mock.MkMessage(secpk, to, 55, w) + + smsv := vectors.MessageSigningVector{ + Unsigned: &smsg.Message, + Cid: smsg.Message.Cid().String(), + CidHexBytes: fmt.Sprintf("%x", smsg.Message.Cid().Bytes()), + PrivateKey: ski.PrivateKey, + Signature: &smsg.Signature, + } + + return []vectors.MessageSigningVector{blsmsv, smsv} +} + +func MakeUnsignedMessageVectors() []vectors.UnsignedMessageVector { + froms := []string{ + "t2ch7krq7l35i74rebqbjdsp3ucl47t24e3juxjfa", + "t1pyfq7dg6sq65acyomqvzvbgwni4zllglqffw5dy", + "t1cyg66djxytxhzdq7ynoqfxk7xinp6xsejbeufli", + "t16n7vrq5humzoqll7zg4yw6dta645tuakcoalp6y", + "t1awsiuji4wpbxpzslg36f3wnfxzi4o5gq67tz2mi", + "t14mb3j32uuwajy5b2mliz63isp6zl5xkppzyuhfy", + "t1dzdmyzzdy6q5elobj63eokzv2xnwsp4vm5l6aka", + "t1svd45rkcfpsyqedvvhuv77yvllvu5ygmygjlvka", + "t1mrret5liwh46qde6qhaxrmcwil7jawjeqdijwfq", + "t1ly3ynedw74p4q3ytdnb4stjdkiodrl54moeyxea", + "t1uqexvn66gj4lxkbvmrgposwrlxbyd655o2nayyi", + "t1dwwjod7vw62jzw2eva7gtxohaidjhgh6w2rofui", + "t1slswisymmkfulmvl3jynrnwqi27tkvmsgzhztvy", + "t1e3vymxcdqfkqwz6e6wnxxx6ayuml3vxi5gef4xa", + "t1bgqopgk64ywpprka4citgi62aldclyaegvwvx6y", + "t1aizqgl2klzkzffwu35rufyuzefke2i6ndbewuhi", + "t1mzposcnsd2tc66yu5i3kajtrh5pvwohdjvitcey", + "t1x7xvs6oorrrlefyzn6wlbvaibzj3a2fyt4hsmvq", + "t1ez743nvc4j7qfirwnmxbh4qdqwha3iyalnq4rya", + "t17dvtgkop7cqgi6myjne5kzvrnsbg5wnowjphhwy", + "t1kvar5z3q7dwrfxjqsnuqpq5qsd7mvh2xypblwta", + } + var out []vectors.UnsignedMessageVector + for _, a := range froms { + from, err := address.NewFromString(a) + if err != nil { + panic(err) + } + to, err := address.NewIDAddress(rand.Uint64()) + if err != nil { + panic(err) + } + + params := make([]byte, 32) + rand.Read(params) + + msg := &types.Message{ + To: to, + From: from, + Value: types.NewInt(rand.Uint64()), + Method: abi.MethodNum(rand.Uint64()), + GasPrice: types.NewInt(rand.Uint64()), + GasLimit: rand.Int63(), + Nonce: rand.Uint64(), + Params: params, + } + + ser, err := msg.Serialize() + if err != nil { + panic(err) + } + + out = append(out, vectors.UnsignedMessageVector{ + Message: msg, + HexCbor: fmt.Sprintf("%x", ser), + }) + } + return out +} + +func WriteJsonToFile(fname string, obj interface{}) error { + fi, err := os.Create(fname) + if err != nil { + return err + } + defer fi.Close() + + out, err := json.MarshalIndent(obj, "", " ") + if err != nil { + return err + } + + fi.Write(out) + return nil +} + +func main() { + if err := WriteJsonToFile("block_headers.json", MakeHeaderVectors()); err != nil { + panic(err) + } + if err := WriteJsonToFile("message_signing.json", MakeMessageSigningVectors()); err != nil { + panic(err) + } + if err := WriteJsonToFile("unsigned_messages.json", MakeUnsignedMessageVectors()); err != nil { + panic(err) + } +} diff --git a/chain/vectors/vector_types.go b/chain/vectors/vector_types.go new file mode 100644 index 000000000..73216a049 --- /dev/null +++ b/chain/vectors/vector_types.go @@ -0,0 +1,25 @@ +package vectors + +import ( + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/specs-actors/actors/crypto" +) + +type HeaderVector struct { + Block *types.BlockHeader `json:"block"` + CborHex string `json:"cbor_hex"` + Cid string `json:"cid"` +} + +type MessageSigningVector struct { + Unsigned *types.Message + Cid string + CidHexBytes string + PrivateKey []byte + Signature *crypto.Signature +} + +type UnsignedMessageVector struct { + Message *types.Message `json:"message"` + HexCbor string `json:"hex_cbor"` +} diff --git a/chain/vectors/vectors_test.go b/chain/vectors/vectors_test.go new file mode 100644 index 000000000..f7916e9d1 --- /dev/null +++ b/chain/vectors/vectors_test.go @@ -0,0 +1,45 @@ +package vectors + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "testing" +) + +func LoadVector(t *testing.T, f string, out interface{}) { + p := filepath.Join("../../extern/serialization-vectors", f) + fi, err := os.Open(p) + if err != nil { + t.Fatal(err) + } + + if err := json.NewDecoder(fi).Decode(out); err != nil { + t.Fatal(err) + } +} + +func TestBlockHeaderVectors(t *testing.T) { + var headers []HeaderVector + LoadVector(t, "block_headers.json", &headers) + + for i, hv := range headers { + if hv.Block.Cid().String() != hv.Cid { + t.Fatalf("CID mismatch in test vector %d", i) + } + + data, err := hv.Block.Serialize() + if err != nil { + t.Fatal(err) + } + + if fmt.Sprintf("%x", data) != hv.CborHex { + t.Fatalf("serialized data mismatched for test vector %d", i) + } + } +} + +func TestMessageSigningVectors(t *testing.T) { + // TODO: +} diff --git a/chain/vm/gas.go b/chain/vm/gas.go new file mode 100644 index 000000000..109d51f12 --- /dev/null +++ b/chain/vm/gas.go @@ -0,0 +1,141 @@ +package vm + +import ( + "fmt" + + addr "github.com/filecoin-project/go-address" + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/specs-actors/actors/runtime" + vmr "github.com/filecoin-project/specs-actors/actors/runtime" + "github.com/ipfs/go-cid" +) + +// Pricelist provides prices for operations in the VM. +// +// Note: this interface should be APPEND ONLY since last chain checkpoint +type Pricelist interface { + // OnChainMessage returns the gas used for storing a message of a given size in the chain. + OnChainMessage(msgSize int) int64 + // OnChainReturnValue returns the gas used for storing the response of a message in the chain. + OnChainReturnValue(dataSize int) int64 + + // OnMethodInvocation returns the gas used when invoking a method. + OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) int64 + + // OnIpldGet returns the gas used for storing an object + OnIpldGet(dataSize int) int64 + // OnIpldPut returns the gas used for storing an object + OnIpldPut(dataSize int) int64 + + // OnCreateActor returns the gas used for creating an actor + OnCreateActor() int64 + // OnDeleteActor returns the gas used for deleting an actor + OnDeleteActor() int64 + + OnVerifySignature(sigType crypto.SigType, planTextSize int) int64 + OnHashing(dataSize int) int64 + OnComputeUnsealedSectorCid(proofType abi.RegisteredProof, pieces []abi.PieceInfo) int64 + OnVerifySeal(info abi.SealVerifyInfo) int64 + OnVerifyPost(info abi.PoStVerifyInfo) int64 + OnVerifyConsensusFault() int64 +} + +var prices = map[abi.ChainEpoch]Pricelist{ + abi.ChainEpoch(0): &pricelistV0{ + onChainMessageBase: 0, + onChainMessagePerByte: 2, + onChainReturnValuePerByte: 8, + sendBase: 5, + sendTransferFunds: 5, + sendInvokeMethod: 10, + ipldGetBase: 10, + ipldGetPerByte: 1, + ipldPutBase: 20, + ipldPutPerByte: 2, + createActorBase: 40, // IPLD put + 20 + createActorExtra: 500, + deleteActor: -500, // -createActorExtra + // Dragons: this cost is not persistable, create a LinearCost{a,b} struct that has a `.Cost(x) -> ax + b` + verifySignature: map[crypto.SigType]func(int64) int64{ + crypto.SigTypeBLS: func(x int64) int64 { return 3*x + 2 }, + crypto.SigTypeSecp256k1: func(x int64) int64 { return 3*x + 2 }, + }, + hashingBase: 5, + hashingPerByte: 2, + computeUnsealedSectorCidBase: 100, + verifySealBase: 2000, + verifyPostBase: 700, + verifyConsensusFault: 10, + }, +} + +// PricelistByEpoch finds the latest prices for the given epoch +func PricelistByEpoch(epoch abi.ChainEpoch) Pricelist { + // since we are storing the prices as map or epoch to price + // we need to get the price with the highest epoch that is lower or equal to the `epoch` arg + bestEpoch := abi.ChainEpoch(0) + bestPrice := prices[bestEpoch] + for e, pl := range prices { + // if `e` happened after `bestEpoch` and `e` is earlier or equal to the target `epoch` + if e > bestEpoch && e <= epoch { + bestEpoch = e + bestPrice = pl + } + } + if bestPrice == nil { + panic(fmt.Sprintf("bad setup: no gas prices available for epoch %d", epoch)) + } + return bestPrice +} + +type pricedSyscalls struct { + under vmr.Syscalls + pl Pricelist + chargeGas func(int64) +} + +// Verifies that a signature is valid for an address and plaintext. +func (ps pricedSyscalls) VerifySignature(signature crypto.Signature, signer addr.Address, plaintext []byte) error { + ps.chargeGas(ps.pl.OnVerifySignature(signature.Type, len(plaintext))) + return ps.under.VerifySignature(signature, signer, plaintext) +} + +// Hashes input data using blake2b with 256 bit output. +func (ps pricedSyscalls) HashBlake2b(data []byte) [32]byte { + ps.chargeGas(ps.pl.OnHashing(len(data))) + return ps.under.HashBlake2b(data) +} + +// Computes an unsealed sector CID (CommD) from its constituent piece CIDs (CommPs) and sizes. +func (ps pricedSyscalls) ComputeUnsealedSectorCID(reg abi.RegisteredProof, pieces []abi.PieceInfo) (cid.Cid, error) { + ps.chargeGas(ps.pl.OnComputeUnsealedSectorCid(reg, pieces)) + return ps.under.ComputeUnsealedSectorCID(reg, pieces) +} + +// Verifies a sector seal proof. +func (ps pricedSyscalls) VerifySeal(vi abi.SealVerifyInfo) error { + ps.chargeGas(ps.pl.OnVerifySeal(vi)) + return ps.under.VerifySeal(vi) +} + +// Verifies a proof of spacetime. +func (ps pricedSyscalls) VerifyPoSt(vi abi.PoStVerifyInfo) error { + ps.chargeGas(ps.pl.OnVerifyPost(vi)) + return ps.under.VerifyPoSt(vi) +} + +// Verifies that two block headers provide proof of a consensus fault: +// - both headers mined by the same actor +// - headers are different +// - first header is of the same or lower epoch as the second +// - at least one of the headers appears in the current chain at or after epoch `earliest` +// - the headers provide evidence of a fault (see the spec for the different fault types). +// The parameters are all serialized block headers. The third "extra" parameter is consulted only for +// the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the +// blocks in the parent of h2 (i.e. h2's grandparent). +// Returns nil and an error if the headers don't prove a fault. +func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte, earliest abi.ChainEpoch) (*runtime.ConsensusFault, error) { + ps.chargeGas(ps.pl.OnVerifyConsensusFault()) + return ps.under.VerifyConsensusFault(h1, h2, extra, earliest) +} diff --git a/chain/vm/gas_v0.go b/chain/vm/gas_v0.go new file mode 100644 index 000000000..d0d6ab43d --- /dev/null +++ b/chain/vm/gas_v0.go @@ -0,0 +1,165 @@ +package vm + +import ( + "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/specs-actors/actors/builtin" + "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" +) + +type pricelistV0 struct { + /////////////////////////////////////////////////////////////////////////// + // System operations + /////////////////////////////////////////////////////////////////////////// + + // Gas cost charged to the originator of an on-chain message (regardless of + // whether it succeeds or fails in application) is given by: + // OnChainMessageBase + len(serialized message)*OnChainMessagePerByte + // Together, these account for the cost of message propagation and validation, + // up to but excluding any actual processing by the VM. + // This is the cost a block producer burns when including an invalid message. + onChainMessageBase int64 + onChainMessagePerByte int64 + + // Gas cost charged to the originator of a non-nil return value produced + // by an on-chain message is given by: + // len(return value)*OnChainReturnValuePerByte + onChainReturnValuePerByte int64 + + // Gas cost for any message send execution(including the top-level one + // initiated by an on-chain message). + // This accounts for the cost of loading sender and receiver actors and + // (for top-level messages) incrementing the sender's sequence number. + // Load and store of actor sub-state is charged separately. + sendBase int64 + + // Gas cost charged, in addition to SendBase, if a message send + // is accompanied by any nonzero currency amount. + // Accounts for writing receiver's new balance (the sender's state is + // already accounted for). + sendTransferFunds int64 + + // Gas cost charged, in addition to SendBase, if a message invokes + // a method on the receiver. + // Accounts for the cost of loading receiver code and method dispatch. + sendInvokeMethod int64 + + // Gas cost (Base + len*PerByte) for any Get operation to the IPLD store + // in the runtime VM context. + ipldGetBase int64 + ipldGetPerByte int64 + + // Gas cost (Base + len*PerByte) for any Put operation to the IPLD store + // in the runtime VM context. + // + // Note: these costs should be significantly higher than the costs for Get + // operations, since they reflect not only serialization/deserialization + // but also persistent storage of chain data. + ipldPutBase int64 + ipldPutPerByte int64 + + // Gas cost for creating a new actor (via InitActor's Exec method). + // + // Note: this costs assume that the extra will be partially or totally refunded while + // the base is covering for the put. + createActorBase int64 + createActorExtra int64 + + // Gas cost for deleting an actor. + // + // Note: this partially refunds the create cost to incentivise the deletion of the actors. + deleteActor int64 + + verifySignature map[crypto.SigType]func(len int64) int64 + + hashingBase int64 + hashingPerByte int64 + + computeUnsealedSectorCidBase int64 + verifySealBase int64 + verifyPostBase int64 + verifyConsensusFault int64 +} + +var _ Pricelist = (*pricelistV0)(nil) + +// OnChainMessage returns the gas used for storing a message of a given size in the chain. +func (pl *pricelistV0) OnChainMessage(msgSize int) int64 { + return pl.onChainMessageBase + pl.onChainMessagePerByte*int64(msgSize) +} + +// OnChainReturnValue returns the gas used for storing the response of a message in the chain. +func (pl *pricelistV0) OnChainReturnValue(dataSize int) int64 { + return int64(dataSize) * pl.onChainReturnValuePerByte +} + +// OnMethodInvocation returns the gas used when invoking a method. +func (pl *pricelistV0) OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) int64 { + ret := pl.sendBase + if value != abi.NewTokenAmount(0) { + ret += pl.sendTransferFunds + } + if methodNum != builtin.MethodSend { + ret += pl.sendInvokeMethod + } + return ret +} + +// OnIpldGet returns the gas used for storing an object +func (pl *pricelistV0) OnIpldGet(dataSize int) int64 { + return pl.ipldGetBase + int64(dataSize)*pl.ipldGetPerByte +} + +// OnIpldPut returns the gas used for storing an object +func (pl *pricelistV0) OnIpldPut(dataSize int) int64 { + return pl.ipldPutBase + int64(dataSize)*pl.ipldPutPerByte +} + +// OnCreateActor returns the gas used for creating an actor +func (pl *pricelistV0) OnCreateActor() int64 { + return pl.createActorBase + pl.createActorExtra +} + +// OnDeleteActor returns the gas used for deleting an actor +func (pl *pricelistV0) OnDeleteActor() int64 { + return pl.deleteActor +} + +// OnVerifySignature +func (pl *pricelistV0) OnVerifySignature(sigType crypto.SigType, planTextSize int) int64 { + costFn, ok := pl.verifySignature[sigType] + if !ok { + // TODO: fix retcode to be int64 + panic(aerrors.Newf(uint8(exitcode.SysErrInternal&0xff), "Cost function for signature type %d not supported", sigType)) + } + return costFn(int64(planTextSize)) +} + +// OnHashing +func (pl *pricelistV0) OnHashing(dataSize int) int64 { + return pl.hashingBase + int64(dataSize)*pl.hashingPerByte +} + +// OnComputeUnsealedSectorCid +func (pl *pricelistV0) OnComputeUnsealedSectorCid(proofType abi.RegisteredProof, pieces []abi.PieceInfo) int64 { + // TODO: this needs more cost tunning, check with @lotus + return pl.computeUnsealedSectorCidBase +} + +// OnVerifySeal +func (pl *pricelistV0) OnVerifySeal(info abi.SealVerifyInfo) int64 { + // TODO: this needs more cost tunning, check with @lotus + return pl.verifySealBase +} + +// OnVerifyPost +func (pl *pricelistV0) OnVerifyPost(info abi.PoStVerifyInfo) int64 { + // TODO: this needs more cost tunning, check with @lotus + return pl.verifyPostBase +} + +// OnVerifyConsensusFault +func (pl *pricelistV0) OnVerifyConsensusFault() int64 { + return pl.verifyConsensusFault +} diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index 4740c44d5..492900990 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/binary" - "runtime/debug" "github.com/filecoin-project/go-address" "github.com/filecoin-project/specs-actors/actors/abi" @@ -28,11 +27,12 @@ import ( type Runtime struct { ctx context.Context - vm *VM - state *state.StateTree - msg *types.Message - height abi.ChainEpoch - cst cbor.IpldStore + vm *VM + state *state.StateTree + msg *types.Message + height abi.ChainEpoch + cst cbor.IpldStore + pricelist Pricelist gasAvailable int64 gasUsed int64 @@ -44,8 +44,7 @@ type Runtime struct { originNonce uint64 internalExecutions []*ExecutionResult - // the first internal call has a value of 1 for this field - internalCallCounter int64 + numActorsCreated uint64 } func (rs *Runtime) ResolveAddress(address address.Address) (ret address.Address, ok bool) { @@ -78,13 +77,11 @@ func (rs *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.Act defer func() { if r := recover(); r != nil { if ar, ok := r.(aerrors.ActorError); ok { - log.Warn("VM.Call failure: ", ar) - debug.PrintStack() + log.Errorf("VM.Call failure: %+v", ar) aerr = ar return } - debug.PrintStack() - log.Errorf("ERROR") + log.Errorf("spec actors failure: %s", r) aerr = aerrors.Newf(1, "spec actors failure: %s", r) } }() @@ -168,7 +165,7 @@ func (rt *Runtime) NewActorAddress() address.Address { if err := binary.Write(&b, binary.BigEndian, rt.originNonce); err != nil { rt.Abortf(exitcode.ErrSerialization, "writing nonce address into a buffer: %v", err) } - if err := binary.Write(&b, binary.BigEndian, rt.internalCallCounter); err != nil { // TODO: expose on vm + if err := binary.Write(&b, binary.BigEndian, rt.numActorsCreated); err != nil { // TODO: expose on vm rt.Abortf(exitcode.ErrSerialization, "writing callSeqNum address into a buffer: %v", err) } addr, err := address.NewActorAddress(b.Bytes()) @@ -176,10 +173,12 @@ func (rt *Runtime) NewActorAddress() address.Address { rt.Abortf(exitcode.ErrSerialization, "create actor address: %v", err) } + rt.incrementNumActorsCreated() return addr } func (rt *Runtime) CreateActor(codeId cid.Cid, address address.Address) { + rt.ChargeGas(rt.Pricelist().OnCreateActor()) var err error err = rt.state.SetActor(address, &types.Actor{ @@ -194,6 +193,7 @@ func (rt *Runtime) CreateActor(codeId cid.Cid, address address.Address) { } func (rt *Runtime) DeleteActor() { + rt.ChargeGas(rt.Pricelist().OnDeleteActor()) act, err := rt.state.GetActor(rt.Message().Receiver()) if err != nil { rt.Abortf(exitcode.SysErrInternal, "failed to load actor in delete actor: %s", err) @@ -314,6 +314,7 @@ func (rt *Runtime) internalSend(to address.Address, method abi.MethodNum, value return nil, aerrors.Fatalf("snapshot failed: %s", err) } defer st.ClearSnapshot() + rt.ChargeGas(rt.Pricelist().OnMethodInvocation(value, method)) ret, errSend, subrt := rt.vm.send(ctx, msg, rt, 0) if errSend != nil { @@ -339,7 +340,7 @@ func (rt *Runtime) internalSend(to address.Address, method abi.MethodNum, value if subrt != nil { er.Subcalls = subrt.internalExecutions - rt.internalCallCounter = subrt.internalCallCounter + rt.numActorsCreated = subrt.numActorsCreated } rt.internalExecutions = append(rt.internalExecutions, &er) return ret, errSend @@ -400,8 +401,6 @@ func (rt *Runtime) GetBalance(a address.Address) (types.BigInt, aerrors.ActorErr } func (rt *Runtime) stateCommit(oldh, newh cid.Cid) aerrors.ActorError { - rt.ChargeGas(gasCommit) - // TODO: we can make this more efficient in the future... act, err := rt.state.GetActor(rt.Message().Receiver()) if err != nil { @@ -422,8 +421,24 @@ func (rt *Runtime) stateCommit(oldh, newh cid.Cid) aerrors.ActorError { } func (rt *Runtime) ChargeGas(toUse int64) { - rt.gasUsed = rt.gasUsed + toUse - if rt.gasUsed > rt.gasAvailable { - rt.Abortf(exitcode.SysErrOutOfGas, "not enough gas: used=%d, available=%d", rt.gasUsed, rt.gasAvailable) + err := rt.chargeGasSafe(toUse) + if err != nil { + panic(err) } } + +func (rt *Runtime) chargeGasSafe(toUse int64) aerrors.ActorError { + rt.gasUsed += toUse + if rt.gasUsed > rt.gasAvailable { + return aerrors.Newf(uint8(exitcode.SysErrOutOfGas), "not enough gas: used=%d, available=%d", rt.gasUsed, rt.gasAvailable) + } + return nil +} + +func (rt *Runtime) Pricelist() Pricelist { + return rt.pricelist +} + +func (rt *Runtime) incrementNumActorsCreated() { + rt.numActorsCreated++ +} diff --git a/chain/vm/validation_test.go b/chain/vm/validation_test.go index b650549a1..26e02fb56 100644 --- a/chain/vm/validation_test.go +++ b/chain/vm/validation_test.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" "runtime" + "strings" "testing" suites "github.com/filecoin-project/chain-validation/suites" @@ -37,6 +38,9 @@ func init() { /* tests to skip go here */ tipset.TestInternalMessageApplicationFailure, tipset.TestInvalidSenderAddress, + tipset.TestBlockMessageDeduplication, + tipset.TestMinerSubmitFallbackPoSt, + tipset.TestMinerMissPoStChallengeWindow, }} } @@ -46,7 +50,9 @@ func TestChainValidationMessageSuite(t *testing.T) { if TestSuiteSkipper.Skip(testCase) { continue } - testCase(t, f) + t.Run(caseName(testCase), func(t *testing.T) { + testCase(t, f) + }) } } @@ -56,6 +62,14 @@ func TestChainValidationTipSetSuite(t *testing.T) { if TestSuiteSkipper.Skip(testCase) { continue } - testCase(t, f) + t.Run(caseName(testCase), func(t *testing.T) { + testCase(t, f) + }) } } + +func caseName(testCase suites.TestCase) string { + fqName := runtime.FuncForPC(reflect.ValueOf(testCase).Pointer()).Name() + toks := strings.Split(fqName, ".") + return toks[len(toks)-1] +} diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 29d0afc00..e803e01d9 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -92,22 +92,22 @@ var _ cbor.IpldBlockstore = (*gasChargingBlocks)(nil) type gasChargingBlocks struct { chargeGas func(int64) + pricelist Pricelist under cbor.IpldBlockstore } func (bs *gasChargingBlocks) Get(c cid.Cid) (block.Block, error) { - bs.chargeGas(gasGetObj) blk, err := bs.under.Get(c) if err != nil { return nil, aerrors.Escalate(err, "failed to get block from blockstore") } - bs.chargeGas(int64(len(blk.RawData())) * gasGetPerByte) + bs.chargeGas(bs.pricelist.OnIpldGet(len(blk.RawData()))) return blk, nil } func (bs *gasChargingBlocks) Put(blk block.Block) error { - bs.chargeGas(gasPutObj + int64(len(blk.RawData()))*gasPutPerByte) + bs.chargeGas(bs.pricelist.OnIpldPut(len(blk.RawData()))) if err := bs.under.Put(blk); err != nil { return aerrors.Escalate(err, "failed to write data to disk") @@ -115,7 +115,7 @@ func (bs *gasChargingBlocks) Put(blk block.Block) error { return nil } -func (vm *VM) makeRuntime(ctx context.Context, msg *types.Message, origin address.Address, originNonce uint64, usedGas int64, icc int64) *Runtime { +func (vm *VM) makeRuntime(ctx context.Context, msg *types.Message, origin address.Address, originNonce uint64, usedGas int64, nac uint64) *Runtime { rt := &Runtime{ ctx: ctx, vm: vm, @@ -124,16 +124,21 @@ func (vm *VM) makeRuntime(ctx context.Context, msg *types.Message, origin addres origin: origin, originNonce: originNonce, height: vm.blockHeight, - sys: vm.Syscalls, - gasUsed: usedGas, - gasAvailable: msg.GasLimit, - internalCallCounter: icc, + gasUsed: usedGas, + gasAvailable: msg.GasLimit, + numActorsCreated: nac, + pricelist: PricelistByEpoch(vm.blockHeight), } rt.cst = &cbor.BasicIpldStore{ - Blocks: &gasChargingBlocks{rt.ChargeGas, vm.cst.Blocks}, + Blocks: &gasChargingBlocks{rt.ChargeGas, rt.pricelist, vm.cst.Blocks}, Atlas: vm.cst.Atlas, } + rt.sys = pricedSyscalls{ + under: vm.Syscalls, + chargeGas: rt.ChargeGas, + pl: rt.pricelist, + } return rt } @@ -193,6 +198,8 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime, return nil, aerrors.Absorb(err, 1, "could not find source actor"), nil } + gasUsed := gasCharge + toActor, err := st.GetActor(msg.To) if err != nil { if xerrors.Is(err, init_.ErrAddressNotFound) { @@ -201,31 +208,34 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime, return nil, aerrors.Absorb(err, 1, "could not create account"), nil } toActor = a + gasUsed += PricelistByEpoch(vm.blockHeight).OnCreateActor() } else { return nil, aerrors.Escalate(err, "getting actor"), nil } } - gasUsed := gasCharge origin := msg.From on := msg.Nonce - var icc int64 = 0 + var nac uint64 = 0 if parent != nil { gasUsed = parent.gasUsed + gasUsed origin = parent.origin on = parent.originNonce - icc = parent.internalCallCounter + 1 + nac = parent.numActorsCreated } - rt := vm.makeRuntime(ctx, msg, origin, on, gasUsed, icc) + rt := vm.makeRuntime(ctx, msg, origin, on, gasUsed, nac) if parent != nil { defer func() { parent.gasUsed = rt.gasUsed }() } - if types.BigCmp(msg.Value, types.NewInt(0)) != 0 { - rt.ChargeGas(gasFundTransfer) + aerr := rt.chargeGasSafe(rt.Pricelist().OnMethodInvocation(msg.Value, msg.Method)) + if aerr != nil { + return nil, aerr, rt + } + if types.BigCmp(msg.Value, types.NewInt(0)) != 0 { if err := Transfer(fromActor, toActor, msg.Value); err != nil { return nil, aerrors.Absorb(err, 1, "failed to transfer funds"), nil } @@ -273,11 +283,13 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, return nil, err } + pl := PricelistByEpoch(vm.blockHeight) serMsg, err := msg.Serialize() if err != nil { return nil, xerrors.Errorf("could not serialize message: %w", err) } - msgGasCost := int64(len(serMsg)) * gasPerMessageByte + msgGasCost := pl.OnChainMessage(len(serMsg)) + // TODO: charge miner?? if msgGasCost > msg.GasLimit { return &ApplyRet{ MessageReceipt: types.MessageReceipt{ @@ -288,10 +300,6 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, } st := vm.cstate - if err := st.Snapshot(ctx); err != nil { - return nil, xerrors.Errorf("snapshot failed: %w", err) - } - defer st.ClearSnapshot() fromActor, err := st.GetActor(msg.From) if err != nil { @@ -310,7 +318,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, return &ApplyRet{ MessageReceipt: types.MessageReceipt{ ExitCode: exitcode.SysErrInvalidCallSeqNum, - GasUsed: msg.GasLimit, + GasUsed: 0, }, }, nil } @@ -333,8 +341,21 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, fromActor.Nonce++ + if err := st.Snapshot(ctx); err != nil { + return nil, xerrors.Errorf("snapshot failed: %w", err) + } + defer st.ClearSnapshot() + ret, actorErr, rt := vm.send(ctx, msg, nil, msgGasCost) + { + actorErr2 := rt.chargeGasSafe(rt.Pricelist().OnChainReturnValue(len(ret))) + if actorErr == nil { + //TODO: Ambigous what to do in this case + actorErr = actorErr2 + } + } + if aerrors.IsFatal(actorErr) { return nil, xerrors.Errorf("[from=%s,to=%s,n=%d,m=%d,h=%d] fatal error: %w", msg.From, msg.To, msg.Nonce, msg.Method, vm.blockHeight, actorErr) } @@ -353,6 +374,9 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, } } else { gasUsed = rt.gasUsed + if gasUsed < 0 { + gasUsed = 0 + } // refund unused gas refund := types.BigMul(types.NewInt(uint64(msg.GasLimit-gasUsed)), msg.GasPrice) if err := Transfer(gasHolder, fromActor, refund); err != nil { @@ -547,7 +571,6 @@ func (vm *VM) Invoke(act *types.Actor, rt *Runtime, method abi.MethodNum, params defer func() { rt.ctx = oldCtx }() - rt.ChargeGas(gasInvoke) ret, err := vm.inv.Invoke(act, rt, method, params) if err != nil { return nil, err diff --git a/extern/serialization-vectors b/extern/serialization-vectors new file mode 160000 index 000000000..1e778d5bd --- /dev/null +++ b/extern/serialization-vectors @@ -0,0 +1 @@ +Subproject commit 1e778d5bd77f758e83a18c41d10c2649b0e70fef diff --git a/go.mod b/go.mod index 1233a04d9..349cf048f 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/coreos/go-systemd/v22 v22.0.0 github.com/docker/go-units v0.4.0 github.com/elastic/go-sysinfo v1.3.0 - github.com/filecoin-project/chain-validation v0.0.6-0.20200318065243-0ccb5ec3afc5 + github.com/filecoin-project/chain-validation v0.0.6-0.20200320210432-2793319e9867 github.com/filecoin-project/filecoin-ffi v0.0.0-20200304181354-4446ff8a1bb9 github.com/filecoin-project/go-address v0.0.2-0.20200218010043-eb9bb40ed5be github.com/filecoin-project/go-amt-ipld/v2 v2.0.1-0.20200131012142-05d80eeccc5e diff --git a/go.sum b/go.sum index 2b1d2a531..16ee4eab3 100644 --- a/go.sum +++ b/go.sum @@ -102,8 +102,8 @@ github.com/elastic/go-windows v1.0.0 h1:qLURgZFkkrYyTTkvYpsZIgf83AUsdIHfvlJaqaZ7 github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU= github.com/fatih/color v1.8.0 h1:5bzFgL+oy7JITMTxUPJ00n7VxmYd/PdMp5mHFX40/RY= github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGjnw8= -github.com/filecoin-project/chain-validation v0.0.6-0.20200318065243-0ccb5ec3afc5 h1:cr9+8iX+u9fDV53MWqqZw820EyeWVX+h/HCz56JUWb0= -github.com/filecoin-project/chain-validation v0.0.6-0.20200318065243-0ccb5ec3afc5/go.mod h1:7HoEkq8OWN3vGcCZ4SRGxAPeL/mLckS+PNV3F0XmrCs= +github.com/filecoin-project/chain-validation v0.0.6-0.20200320210432-2793319e9867 h1:6+9Khz+vidBfWG7xQ6a2uRpLnd3RhMVcOwJxu3XhvgI= +github.com/filecoin-project/chain-validation v0.0.6-0.20200320210432-2793319e9867/go.mod h1:YTLxUr6gOZpkUaXzLe7OZ4s1dpfJGp2FY/J2/K5DJqc= github.com/filecoin-project/go-address v0.0.0-20200107215422-da8eea2842b5 h1:/MmWluswvDIbuPvBct4q6HeQgVm62O2DzWYTB38kt4A= github.com/filecoin-project/go-address v0.0.0-20200107215422-da8eea2842b5/go.mod h1:SAOwJoakQ8EPjwNIsiakIQKsoKdkcbx8U3IapgCg9R0= github.com/filecoin-project/go-address v0.0.2-0.20200218010043-eb9bb40ed5be h1:TooKBwR/g8jG0hZ3lqe9S5sy2vTUcLOZLlz3M5wGn2E= @@ -141,8 +141,6 @@ github.com/filecoin-project/specs-actors v0.0.0-20200210130641-2d1fbd8672cf/go.m github.com/filecoin-project/specs-actors v0.0.0-20200226200336-94c9b92b2775/go.mod h1:0HAWYrvajFHDgRaKbF0rl+IybVLZL5z4gQ8koCMPhoU= github.com/filecoin-project/specs-actors v0.0.0-20200302223606-0eaf97b10aaf/go.mod h1:0HAWYrvajFHDgRaKbF0rl+IybVLZL5z4gQ8koCMPhoU= github.com/filecoin-project/specs-actors v0.0.0-20200306000749-99e98e61e2a0/go.mod h1:0HAWYrvajFHDgRaKbF0rl+IybVLZL5z4gQ8koCMPhoU= -github.com/filecoin-project/specs-actors v0.0.0-20200311215506-e95895452888 h1:VCrkpFmZuQRyHrUpFTS3K/09cCQDMi/ZJUQ6c4zr1g4= -github.com/filecoin-project/specs-actors v0.0.0-20200311215506-e95895452888/go.mod h1:5WngRgTN5Eo4+0SjCBqLzEr2l6Mj45DrP2606gBhqI0= github.com/filecoin-project/specs-actors v0.0.0-20200312030511-3f5510bf6130 h1:atiWEDtI/gzSm89fL+NyneLN3eHfBd1QPgOZyXPjA5M= github.com/filecoin-project/specs-actors v0.0.0-20200312030511-3f5510bf6130/go.mod h1:5WngRgTN5Eo4+0SjCBqLzEr2l6Mj45DrP2606gBhqI0= github.com/filecoin-project/specs-storage v0.0.0-20200303233430-1a5a408f7513 h1:okBx3lPomwDxlPmRvyP078BwivDfdxNUlpCDhDD0ia8= diff --git a/miner/miner.go b/miner/miner.go index 6f2f00d75..d4f02241e 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -1,12 +1,13 @@ package miner import ( + "bytes" "context" "fmt" "sync" "time" - "github.com/filecoin-project/go-address" + address "github.com/filecoin-project/go-address" "github.com/filecoin-project/specs-actors/actors/abi" "github.com/filecoin-project/specs-actors/actors/crypto" lru "github.com/hashicorp/golang-lru" @@ -348,7 +349,12 @@ func (m *Miner) computeTicket(ctx context.Context, addr address.Address, base *M return nil, err } - input, err := m.api.ChainGetRandomness(ctx, base.ts.Key(), crypto.DomainSeparationTag_TicketProduction, base.ts.Height(), addr.Bytes()) + buf := new(bytes.Buffer) + if err := addr.MarshalCBOR(buf); err != nil { + return nil, xerrors.Errorf("failed to marshal address to cbor: %w", err) + } + + input, err := m.api.ChainGetRandomness(ctx, base.ts.Key(), crypto.DomainSeparationTag_TicketProduction, base.ts.Height(), buf.Bytes()) if err != nil { return nil, err }