code.vegaprotocol.io/vega@v0.79.0/core/datasource/external/ethverifier/verifier_snapshot.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package ethverifier 17 18 import ( 19 "context" 20 "fmt" 21 "slices" 22 23 "code.vegaprotocol.io/vega/core/datasource/external/ethcall" 24 "code.vegaprotocol.io/vega/core/metrics" 25 "code.vegaprotocol.io/vega/core/types" 26 vgcontext "code.vegaprotocol.io/vega/libs/context" 27 "code.vegaprotocol.io/vega/libs/proto" 28 "code.vegaprotocol.io/vega/logging" 29 snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1" 30 31 "golang.org/x/exp/maps" 32 ) 33 34 var ( 35 contractCall = (&types.PayloadEthContractCallEvent{}).Key() 36 lastEthBlock = (&types.PayloadEthOracleLastBlock{}).Key() 37 misc = (&types.PayloadEthVerifierMisc{}).Key() 38 hashKeys = []string{ 39 contractCall, lastEthBlock, misc, 40 } 41 ) 42 43 func (s *Verifier) pendingContractCallEventsPayloadData() *types.PayloadEthContractCallEvent { 44 pendingCallEvents := make([]*ethcall.ContractCallEvent, 0, len(s.pendingCallEvents)) 45 46 for _, p := range s.pendingCallEvents { 47 pendingCallEvents = append(pendingCallEvents, &p.callEvent) 48 } 49 50 return &types.PayloadEthContractCallEvent{ 51 EthContractCallEvent: pendingCallEvents, 52 } 53 } 54 55 func (s *Verifier) serialisePendingContractCallEvents() ([]byte, error) { 56 s.log.Info("serialising pending call events", logging.Int("n", len(s.pendingCallEvents))) 57 58 pl := types.Payload{ 59 Data: s.pendingContractCallEventsPayloadData(), 60 } 61 62 return proto.Marshal(pl.IntoProto()) 63 } 64 65 func (s *Verifier) ethBlockPayloadData(bl *types.EthBlock) *types.PayloadEthOracleLastBlock { 66 if bl != nil { 67 return &types.PayloadEthOracleLastBlock{ 68 EthOracleLastBlock: &types.EthBlock{ 69 Height: bl.Height, 70 Time: bl.Time, 71 }, 72 } 73 } 74 75 return &types.PayloadEthOracleLastBlock{} 76 } 77 78 func (s *Verifier) serialiseLastEthBlock() ([]byte, error) { 79 s.log.Info("serialising last eth block", logging.String("last-eth-block", fmt.Sprintf("%+v", s.lastBlock))) 80 81 pl := types.Payload{ 82 Data: s.ethBlockPayloadData(s.lastBlock), 83 } 84 85 return proto.Marshal(pl.IntoProto()) 86 } 87 88 func (s *Verifier) serialiseMisc() ([]byte, error) { 89 s.log.Info("serialising last eth block", logging.String("last-eth-block", fmt.Sprintf("%+v", s.lastBlock))) 90 91 slice := make([]*snapshotpb.EthVerifierBucket, 0, s.ackedEvts.Size()) 92 iter := s.ackedEvts.events.Iterator() 93 for iter.Next() { 94 v := (iter.Value().(*ackedEvtBucket)) 95 hashes := maps.Keys(v.hashes) 96 slices.Sort(hashes) 97 slice = append(slice, &snapshotpb.EthVerifierBucket{ 98 Ts: v.ts, 99 Hashes: hashes, 100 }) 101 } 102 103 pl := types.Payload{ 104 Data: &types.PayloadEthVerifierMisc{ 105 Misc: &snapshotpb.EthOracleVerifierMisc{ 106 PatchBlock: s.ethBlockPayloadData(s.patchBlock).IntoProto().EthOracleVerifierLastBlock, 107 Buckets: slice, 108 }, 109 }, 110 } 111 112 return proto.Marshal(pl.IntoProto()) 113 } 114 115 func (s *Verifier) serialiseK(serialFunc func() ([]byte, error)) ([]byte, error) { 116 data, err := serialFunc() 117 if err != nil { 118 return nil, err 119 } 120 return data, nil 121 } 122 123 // get the serialised form and hash of the given key. 124 func (s *Verifier) serialise(k string) ([]byte, error) { 125 switch k { 126 case contractCall: 127 return s.serialiseK(s.serialisePendingContractCallEvents) 128 case lastEthBlock: 129 return s.serialiseK(s.serialiseLastEthBlock) 130 case misc: 131 return s.serialiseK(s.serialiseMisc) 132 default: 133 return nil, types.ErrSnapshotKeyDoesNotExist 134 } 135 } 136 137 func (s *Verifier) Namespace() types.SnapshotNamespace { 138 return types.EthereumOracleVerifierSnapshot 139 } 140 141 func (s *Verifier) Keys() []string { 142 return hashKeys 143 } 144 145 func (s *Verifier) Stopped() bool { 146 return false 147 } 148 149 func (s *Verifier) GetState(k string) ([]byte, []types.StateProvider, error) { 150 data, err := s.serialise(k) 151 return data, nil, err 152 } 153 154 func (s *Verifier) LoadState(ctx context.Context, payload *types.Payload) ([]types.StateProvider, error) { 155 if s.Namespace() != payload.Data.Namespace() { 156 return nil, types.ErrInvalidSnapshotNamespace 157 } 158 159 switch pl := payload.Data.(type) { 160 case *types.PayloadEthContractCallEvent: 161 s.restorePendingCallEvents(ctx, pl.EthContractCallEvent) 162 return nil, nil 163 case *types.PayloadEthOracleLastBlock: 164 s.restoreLastEthBlock(ctx, pl.EthOracleLastBlock) 165 return nil, nil 166 case *types.PayloadEthVerifierMisc: 167 s.restoreMisc(ctx, pl.Misc) 168 return nil, nil 169 default: 170 return nil, types.ErrUnknownSnapshotType 171 } 172 } 173 174 func (s *Verifier) OnStateLoaded(ctx context.Context) error { 175 if vgcontext.InProgressUpgradeFrom(ctx, "v0.74.7") { 176 s.patchBlock = s.lastBlock 177 } 178 179 // tell the eth call engine what the last block seen was, so it does not re-trigger calls 180 if s.lastBlock != nil && s.lastBlock.Height > 0 { 181 s.ethEngine.StartAtHeight(s.lastBlock.Height, s.lastBlock.Time) 182 } else { 183 s.ethEngine.Start() 184 } 185 186 return nil 187 } 188 189 func (s *Verifier) restoreSeen(ctx context.Context, buckets []*snapshotpb.EthVerifierBucket) { 190 // if we are executing a protocol upgrade, 191 // let's force bucketing things. This will reduce 192 // increase performance at startup, and everyone is starting 193 // from the same snapshot, so that will keep state consistent 194 if vgcontext.InProgressUpgrade(ctx) { 195 for _, v := range buckets { 196 s.ackedEvts.AddAt(v.Ts, v.Hashes...) 197 } 198 return 199 } 200 201 for _, v := range buckets { 202 s.ackedEvts.RestoreExactAt(v.Ts, v.Hashes...) 203 } 204 } 205 206 func (s *Verifier) restoreLastEthBlock(_ context.Context, lastBlock *types.EthBlock) { 207 s.log.Info("restoring last eth block", logging.String("last-eth-block", fmt.Sprintf("%+v", lastBlock))) 208 s.lastBlock = lastBlock 209 } 210 211 func (s *Verifier) restorePatchBlock(_ context.Context, patchBlock *types.EthBlock) { 212 s.log.Info("restoring patch eth block", logging.String("patch-block", fmt.Sprintf("%+v", patchBlock))) 213 214 // we have no history of what eth events we've seen from before this patch, so we will reject 215 // any that come in that are older 216 s.patchBlock = patchBlock 217 } 218 219 func (s *Verifier) restoreMisc(ctx context.Context, pl *snapshotpb.EthOracleVerifierMisc) { 220 if pl.PatchBlock != nil { 221 s.patchBlock = &types.EthBlock{ 222 Height: pl.PatchBlock.BlockHeight, 223 Time: pl.PatchBlock.BlockTime, 224 } 225 } 226 s.restoreSeen(ctx, pl.Buckets) 227 } 228 229 func (s *Verifier) restorePendingCallEvents(ctx context.Context, 230 results []*ethcall.ContractCallEvent, 231 ) { 232 s.log.Debug("restoring pending call events snapshot", logging.Int("n_pending", len(results))) 233 s.pendingCallEvents = make([]*pendingCallEvent, 0, len(results)) 234 235 // clear up all the metrics 236 seenSpecId := map[string]struct{}{} 237 238 for _, callEvent := range results { 239 if _, ok := seenSpecId[callEvent.SpecId]; !ok { 240 metrics.DataSourceEthVerifierCallGaugeReset(callEvent.SpecId) 241 seenSpecId[callEvent.SpecId] = struct{}{} 242 } 243 244 // if we've upgraded from the patch we need to add the pending events in, but after the upgrade 245 // we don't need to because they will already be there 246 if vgcontext.InProgressUpgradeFrom(ctx, "v0.74.7") { 247 if !s.ensureNotDuplicate(*callEvent) { 248 s.log.Panic("pendingCallEvents's unexpectedly pre-populated when restoring from snapshot") 249 } 250 } 251 252 pending := &pendingCallEvent{ 253 callEvent: *callEvent, 254 check: func(ctx context.Context) error { return s.checkCallEventResult(ctx, *callEvent) }, 255 } 256 257 s.pendingCallEvents = append(s.pendingCallEvents, pending) 258 259 if err := s.witness.RestoreResource(pending, s.onCallEventVerified); err != nil { 260 s.log.Panic("unable to restore pending call event resource", logging.String("ID", pending.GetID()), logging.Error(err)) 261 } 262 263 metrics.DataSourceEthVerifierCallGaugeAdd(1, callEvent.SpecId) 264 } 265 }