github.com/mavryk-network/mvgo@v1.19.9/internal/compose/alpha/task/double_bake.go (about) 1 // Copyright (c) 2023 Blockwatch Data Inc. 2 // Author: alex@blockwatch.cc, abdul@blockwatch.cc 3 package task 4 5 import ( 6 "bytes" 7 "crypto/rand" 8 "encoding/binary" 9 "fmt" 10 11 "github.com/mavryk-network/mvgo/codec" 12 "github.com/mavryk-network/mvgo/internal/compose" 13 "github.com/mavryk-network/mvgo/internal/compose/alpha" 14 "github.com/mavryk-network/mvgo/mavryk" 15 "github.com/mavryk-network/mvgo/rpc" 16 "github.com/mavryk-network/mvgo/signer" 17 18 "github.com/pkg/errors" 19 ) 20 21 var _ alpha.TaskBuilder = (*DoubleBakeTask)(nil) 22 23 func init() { 24 alpha.RegisterTask("double_bake", NewDoubleBakeTask) 25 } 26 27 type DoubleBakeTask struct { 28 TargetTask 29 BakerKey mavryk.PrivateKey 30 } 31 32 func NewDoubleBakeTask() alpha.TaskBuilder { 33 return &DoubleBakeTask{} 34 } 35 36 func (t *DoubleBakeTask) Type() string { 37 return "double_bake" 38 } 39 40 func (t *DoubleBakeTask) Build(ctx compose.Context, task alpha.Task) (*codec.Op, *rpc.CallOptions, error) { 41 if err := t.parse(ctx, task); err != nil { 42 return nil, nil, errors.Wrap(err, "parse") 43 } 44 45 // wait for baking rights to appear for the next block, the block must 46 // actually be baked by this baker, so we look for round 0 only 47 // this means to test this on sandbox the source/baker must be a 48 // sandbox baker 49 ctx.Log.Infof("Waiting for round-0 baking rights for %s...", t.BakerKey.Address()) 50 var ( 51 round int 52 head *rpc.BlockHeaderLogEntry 53 ) 54 done := make(chan struct{}) 55 _, err := ctx.SubscribeBlocks(func(h *rpc.BlockHeaderLogEntry, _ int64, _ int, _ int, _ bool) bool { 56 r, ok, err := t.fetchBakingRights(ctx, t.BakerKey.Address(), h.Hash) 57 if err != nil { 58 ctx.Log.Warnf("fetch baking rights: %v", err) 59 } else if ok && r == 0 { 60 head = h 61 round = r 62 close(done) 63 return true 64 } 65 ctx.Log.Debugf("No round-0 rights in block %d", h.Level) 66 return false 67 }) 68 if err != nil { 69 return nil, nil, err 70 } 71 select { 72 case <-done: 73 ctx.Log.Infof("Found baking rights in block=%d round=%d", head.Level+1, round) 74 case <-ctx.Done(): 75 return nil, nil, ctx.Err() 76 } 77 78 // produce random block headers (for NEXT block!) 79 b1 := t.randomBlock(ctx, head, round) 80 b2 := t.randomBlock(ctx, head, round) 81 82 // order blocks by hash 83 if bytes.Compare(b1.Hash().Bytes(), b2.Hash().Bytes()) > 0 { 84 b1, b2 = b2, b1 85 } 86 87 // pack into evidence op 88 op := codec.NewOp(). 89 WithSource(t.Source). // required for compose simulation mode 90 WithContents(&codec.DoubleBakingEvidence{ 91 Bh1: b1, 92 Bh2: b2, 93 }) 94 95 // wait one block for sending 96 ctx.Log.Debug("Wait next block") 97 if err := ctx.WaitNumBlocks(1); err != nil { 98 return nil, nil, err 99 } 100 101 opts := rpc.NewCallOptions() 102 opts.Signer = signer.NewFromKey(t.Key) 103 opts.IgnoreLimits = true 104 105 return op, opts, nil 106 } 107 108 func (t *DoubleBakeTask) Validate(ctx compose.Context, task alpha.Task) error { 109 return t.parse(ctx, task) 110 } 111 112 func (t *DoubleBakeTask) parse(ctx compose.Context, task alpha.Task) (err error) { 113 if err = t.TargetTask.parse(ctx, task); err != nil { 114 return err 115 } 116 if t.BakerKey, err = ctx.ResolvePrivateKey(task.Destination); err != nil { 117 return errors.Wrap(err, "destination key") 118 } 119 return 120 } 121 122 func (t *DoubleBakeTask) randomBlock(ctx compose.Context, head *rpc.BlockHeaderLogEntry, round int) codec.BlockHeader { 123 // generate a random block header 124 h := codec.BlockHeader{ 125 Level: int32(head.Level + 1), 126 Proto: byte(head.Proto), 127 Predecessor: head.Hash, 128 Timestamp: head.Timestamp, 129 ValidationPass: byte(head.ValidationPass), 130 Fitness: head.Fitness, 131 ProofOfWorkNonce: head.Pow(), 132 PayloadRound: round, 133 OperationsHash: head.OperationsHash, 134 Context: head.Context, 135 LbVote: mavryk.FeatureVotePass, 136 AiVote: mavryk.FeatureVotePass, 137 } 138 // adjust fitness 139 f2 := make([]byte, len(h.Fitness[1])) 140 binary.BigEndian.PutUint32(f2, uint32(head.Level+1)) 141 h.Fitness[1] = f2 142 143 // randomize payload hash 144 rand.Read(h.PayloadHash[:]) 145 h.WithChainId(ctx.Params().ChainId) // Tenderbake block signing needs chain id 146 147 // sign the block 148 if err := h.Sign(t.BakerKey); err != nil { 149 ctx.Log.Errorf("signing random block: %v", err) 150 } 151 return h 152 } 153 154 func (t *DoubleBakeTask) fetchBakingRights(ctx compose.Context, addr mavryk.Address, id mavryk.BlockHash) (int, bool, error) { 155 u := fmt.Sprintf("/chains/main/blocks/%s/helpers/baking_rights?delegate=%s&max_round=64", id, addr) 156 var rights []struct { 157 Round int `json:"round"` 158 } 159 if err := ctx.Fetch(u, &rights); err != nil { 160 return 0, false, err 161 } 162 if len(rights) == 0 { 163 return 0, false, nil 164 } 165 return rights[0].Round, true, nil 166 }