diff --git a/cmd/utils/hierarchical_coordinator.go b/cmd/utils/hierarchical_coordinator.go index 6246d29479..0fc96b0a96 100644 --- a/cmd/utils/hierarchical_coordinator.go +++ b/cmd/utils/hierarchical_coordinator.go @@ -981,7 +981,7 @@ func (hc *HierarchicalCoordinator) BuildPendingHeaders(wo *types.WorkObject, ord log.Global.Debug("Entropy: ", common.BigBitsToBits(entropy)) nodeSet, exists := hc.Get(entropy) if !exists { - log.Global.WithFields(log.Fields{"entropy": common.BigBitsToBits(entropy), "order": order, "number": wo.NumberArray(), "hash": wo.Hash()}).Debug("NodeSet not found for entropy") + log.Global.WithFields(log.Fields{"entropy": common.BigBitsToBits(entropy), "order": order, "number": wo.NumberArray(), "hash": wo.Hash()}).Trace("NodeSet not found for entropy") } if nodeSet.Extendable(wo, order) { @@ -997,7 +997,7 @@ func (hc *HierarchicalCoordinator) BuildPendingHeaders(wo *types.WorkObject, ord } hc.Add(newSetEntropy, newNodeSet, newPendingHeaders) } else { - log.Global.WithFields(log.Fields{"entropy": common.BigBitsToBits(entropy), "order": order, "number": wo.NumberArray(), "hash": wo.Hash()}).Debug("NodeSet not found for entropy") + log.Global.WithFields(log.Fields{"entropy": common.BigBitsToBits(entropy), "order": order, "number": wo.NumberArray(), "hash": wo.Hash()}).Trace("NodeSet not found for entropy") } misses++ if misses > threshold { diff --git a/common/types.go b/common/types.go index 6d89c9072b..5c381e14f5 100644 --- a/common/types.go +++ b/common/types.go @@ -699,6 +699,13 @@ func ZeroInternal(nodeLocation Location) InternalAddress { return InternalAddress{nodeLocation.BytePrefix()} } +// OneInternal returns an address starting with the byte prefix and ending in 1 +func OneInternal(nodeLocation Location) InternalAddress { + one := ZeroInternal(nodeLocation) + one[AddressLength-1] = 1 + return one +} + func ZeroAddress(nodeLocation Location) Address { internal := InternalAddress{nodeLocation.BytePrefix()} return Address{&internal} diff --git a/core/headerchain.go b/core/headerchain.go index 6918576bc9..e6e5be1d70 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -528,6 +528,26 @@ func (hc *HeaderChain) SetCurrentHeader(head *types.WorkObject) error { } hc.headerDb.Delete(key) } + createdCoinbaseKeys, err := rawdb.ReadCreatedCoinbaseLockupKeys(hc.headerDb, prevHeader.Hash()) + if err != nil { + return err + } + for _, key := range createdCoinbaseKeys { + if len(key) != rawdb.CoinbaseLockupKeyLength { + return fmt.Errorf("invalid created coinbase key length: %d", len(key)) + } + hc.headerDb.Delete(key) + } + deletedCoinbases, err := rawdb.ReadDeletedCoinbaseLockups(hc.headerDb, prevHeader.Hash()) + if err != nil { + return err + } + for key, coinbase := range deletedCoinbases { + if len(key) != rawdb.CoinbaseLockupKeyLength { + return fmt.Errorf("invalid deleted coinbase key length: %d", len(key)) + } + hc.headerDb.Put(key[:], coinbase) + } } prevHeader = hc.GetHeaderByHash(prevHeader.ParentHash(hc.NodeCtx())) if prevHeader == nil { @@ -592,6 +612,26 @@ func (hc *HeaderChain) SetCurrentHeader(head *types.WorkObject) error { for _, key := range utxoKeys { hc.headerDb.Delete(key) } + createdCoinbaseKeys, err := rawdb.ReadCreatedCoinbaseLockupKeys(hc.headerDb, prevHeader.Hash()) + if err != nil { + return err + } + for _, key := range createdCoinbaseKeys { + if len(key) != rawdb.CoinbaseLockupKeyLength { + return fmt.Errorf("invalid created coinbase key length: %d", len(key)) + } + hc.headerDb.Delete(key) + } + deletedCoinbases, err := rawdb.ReadDeletedCoinbaseLockups(hc.headerDb, prevHeader.Hash()) + if err != nil { + return err + } + for key, coinbase := range deletedCoinbases { + if len(key) != rawdb.CoinbaseLockupKeyLength { + return fmt.Errorf("invalid deleted coinbase key length: %d", len(key)) + } + hc.headerDb.Put(key[:], coinbase) + } } } for k := len(prevHashStack) - 1; k >= 0; k-- { diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 156d814c07..dae99998ac 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -24,9 +24,9 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/core/types" + "github.com/dominant-strategies/go-quai/crypto/multiset" "github.com/dominant-strategies/go-quai/ethdb" "github.com/dominant-strategies/go-quai/log" - "github.com/dominant-strategies/go-quai/crypto/multiset" "github.com/dominant-strategies/go-quai/params" "google.golang.org/protobuf/proto" ) @@ -1567,6 +1567,77 @@ func DeleteCreatedUTXOKeys(db ethdb.KeyValueWriter, blockHash common.Hash) { } } +func WriteCreatedCoinbaseLockupKeys(db ethdb.KeyValueWriter, blockHash common.Hash, keys [][]byte) error { + protoKeys := &types.ProtoKeys{Keys: make([][]byte, 0, len(keys))} + protoKeys.Keys = append(protoKeys.Keys, keys...) + + data, err := proto.Marshal(protoKeys) + if err != nil { + db.Logger().WithField("err", err).Fatal("Failed to rlp encode utxo") + } + return db.Put(createdCoinbaseLockupsKey(blockHash), data) +} + +func ReadCreatedCoinbaseLockupKeys(db ethdb.Reader, blockHash common.Hash) ([][]byte, error) { + // Try to look up the data in leveldb. + data, _ := db.Get(createdCoinbaseLockupsKey(blockHash)) + if len(data) == 0 { + return nil, nil + } + protoKeys := new(types.ProtoKeys) + if err := proto.Unmarshal(data, protoKeys); err != nil { + return nil, err + } + return protoKeys.Keys, nil +} + +func DeleteCreatedCoinbaseLockupKeys(db ethdb.KeyValueWriter, blockHash common.Hash) { + if err := db.Delete(createdCoinbaseLockupsKey(blockHash)); err != nil { + db.Logger().WithField("err", err).Fatal("Failed to delete created coinbase lockup keys") + } +} + +func WriteDeletedCoinbaseLockups(db ethdb.KeyValueWriter, blockHash common.Hash, deletedLockups map[[47]byte][]byte) error { + protoKeysAndValues := &types.ProtoKeysAndValues{KeysAndValues: make([]*types.ProtoKeyValue, 0, len(deletedLockups))} + for key, value := range deletedLockups { + protoKeysAndValues.KeysAndValues = append(protoKeysAndValues.KeysAndValues, &types.ProtoKeyValue{ + Key: key[:], + Value: value, + }) + } + data, err := proto.Marshal(protoKeysAndValues) + if err != nil { + db.Logger().WithField("err", err).Fatal("Failed to rlp encode utxo") + } + return db.Put(deletedCoinbaseLockupsKey(blockHash), data) +} + +func ReadDeletedCoinbaseLockups(db ethdb.Reader, blockHash common.Hash) (map[[47]byte][]byte, error) { + // Try to look up the data in leveldb. + data, _ := db.Get(deletedCoinbaseLockupsKey(blockHash)) + if len(data) == 0 { + return nil, nil + } + protoKeysAndValues := new(types.ProtoKeysAndValues) + if err := proto.Unmarshal(data, protoKeysAndValues); err != nil { + return nil, err + } + deletedLockups := make(map[[47]byte][]byte) + for _, keyValue := range protoKeysAndValues.KeysAndValues { + if len(keyValue.Key) != 47 { + return nil, fmt.Errorf("invalid key length %d", len(keyValue.Key)) + } + deletedLockups[[47]byte(keyValue.Key)] = keyValue.Value + } + return deletedLockups, nil +} + +func DeleteDeletedCoinbaseLockups(db ethdb.KeyValueWriter, blockHash common.Hash) { + if err := db.Delete(deletedCoinbaseLockupsKey(blockHash)); err != nil { + db.Logger().WithField("err", err).Fatal("Failed to delete deleted coinbase lockups") + } +} + func ReadUTXOSetSize(db ethdb.Reader, blockHash common.Hash) uint64 { data, _ := db.Get(utxoSetSizeKey(blockHash)) if len(data) == 0 { @@ -1710,3 +1781,84 @@ func DeleteUtxoToBlockHeight(db ethdb.KeyValueWriter, txHash common.Hash, index db.Logger().WithField("err", err).Fatal("Failed to delete utxo to block height") } } + +func ReadCoinbaseLockup(db ethdb.KeyValueReader, batch ethdb.Batch, ownerContract common.Address, beneficiaryMiner common.Address, lockupByte byte, epoch uint32) (*big.Int, uint32, uint16, common.Address) { + deleted, data := batch.GetPending(CoinbaseLockupKey(ownerContract, beneficiaryMiner, lockupByte, epoch)) + if deleted { + return new(big.Int), 0, 0, common.Zero + } else if data != nil { + amount := new(big.Int).SetBytes(data[:32]) + blockHeight := binary.BigEndian.Uint32(data[32:36]) + elements := binary.BigEndian.Uint16(data[36:38]) + var delegate common.Address + if len(data) == 58 { + delegate = common.BytesToAddress(data[38:], db.Location()) + } else { + delegate = common.Zero + } + return amount, blockHeight, elements, delegate + } + // If the data is not in the batch, try to look up the data in leveldb + data, _ = db.Get(CoinbaseLockupKey(ownerContract, beneficiaryMiner, lockupByte, epoch)) + if len(data) == 0 { + return new(big.Int), 0, 0, common.Zero + } + amount := new(big.Int).SetBytes(data[:32]) + blockHeight := binary.BigEndian.Uint32(data[32:36]) + elements := binary.BigEndian.Uint16(data[36:38]) + var delegate common.Address + if len(data) == 58 { + delegate = common.BytesToAddress(data[38:], db.Location()) + } else { + delegate = common.Zero + } + return amount, blockHeight, elements, delegate +} + +func WriteCoinbaseLockup(db ethdb.KeyValueWriter, ownerContract common.Address, beneficiaryMiner common.Address, lockupByte byte, epoch uint32, amount *big.Int, blockHeight uint32, elements uint16, delegate common.Address) ([]byte, error) { + data := make([]byte, 38) + amountBytes := amount.Bytes() + if len(amountBytes) > 32 { + return nil, fmt.Errorf("amount is too large") + } + // Right-align amountBytes in data[:32] + copy(data[32-len(amountBytes):32], amountBytes) + binary.BigEndian.PutUint32(data[32:36], blockHeight) + binary.BigEndian.PutUint16(data[36:38], elements) + if !delegate.Equal(common.Zero) { + data = append(data, delegate.Bytes()...) + } + key := CoinbaseLockupKey(ownerContract, beneficiaryMiner, lockupByte, epoch) + if err := db.Put(key, data); err != nil { + db.Logger().WithField("err", err).Fatal("Failed to store coinbase lockup") + } + return key, nil +} + +func WriteCoinbaseLockupToMap(coinbaseMap map[[47]byte][]byte, key [47]byte, amount *big.Int, blockHeight uint32, elements uint16, delegate common.Address) error { + data := make([]byte, 38) + amountBytes := amount.Bytes() + if len(amountBytes) > 32 { + return fmt.Errorf("amount is too large") + } + // Right-align amountBytes in data[:32] + copy(data[32-len(amountBytes):32], amountBytes) + binary.BigEndian.PutUint32(data[32:36], blockHeight) + binary.BigEndian.PutUint16(data[36:38], elements) + if !delegate.Equal(common.Zero) { + data = append(data, delegate.Bytes()...) + } + coinbaseMap[[47]byte(key)] = data + return nil +} + +func DeleteCoinbaseLockup(db ethdb.KeyValueWriter, ownerContract common.Address, beneficiaryMiner common.Address, lockupByte byte, epoch uint32) [47]byte { + key := CoinbaseLockupKey(ownerContract, beneficiaryMiner, lockupByte, epoch) + if err := db.Delete(key); err != nil { + db.Logger().WithField("err", err).Fatal("Failed to delete coinbase lockup") + } + if len(key) != 47 { + db.Logger().Fatal("CoinbaseLockupKey is not 47 bytes") + } + return [47]byte(key) +} diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go index 0a71d7a607..a19c251830 100644 --- a/core/rawdb/accessors_chain_test.go +++ b/core/rawdb/accessors_chain_test.go @@ -59,7 +59,7 @@ func TestHeaderStorage(t *testing.T) { woBody := types.EmptyWorkObjectBody() woBody.SetHeader(header) - woHeader := types.NewWorkObjectHeader(header.Hash(), common.Hash{1}, big.NewInt(1), big.NewInt(30000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 1, common.LocationFromAddressBytes([]byte{0x01, 0x01}), common.BytesToAddress([]byte{1}, common.Location{0, 0})) + woHeader := types.NewWorkObjectHeader(header.Hash(), common.Hash{1}, big.NewInt(1), big.NewInt(30000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 1, common.LocationFromAddressBytes([]byte{0x01, 0x01}), common.BytesToAddress([]byte{1}, common.Location{0, 0}), []byte{0, 1, 2, 3}) wo := types.NewWorkObject(woHeader, woBody, nil) @@ -190,7 +190,7 @@ func TestPbCacheStorage(t *testing.T) { woBody := types.EmptyWorkObjectBody() woBody.SetHeader(types.EmptyHeader()) - woHeader := types.NewWorkObjectHeader(types.EmptyRootHash, types.EmptyRootHash, big.NewInt(11), big.NewInt(30000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 1, common.LocationFromAddressBytes([]byte{0x01, 0x01}), common.BytesToAddress([]byte{1}, common.Location{0, 0})) + woHeader := types.NewWorkObjectHeader(types.EmptyRootHash, types.EmptyRootHash, big.NewInt(11), big.NewInt(30000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 1, common.LocationFromAddressBytes([]byte{0x01, 0x01}), common.BytesToAddress([]byte{1}, common.Location{0, 0}), []byte{0, 1, 2, 3}) wo := types.NewWorkObject(woHeader, woBody, nil) WritePbCacheBody(db, common.Hash{1}, wo) @@ -320,7 +320,7 @@ func TestBlockHashesIterator(t *testing.T) { blockHeader := types.EmptyHeader() blockBody := types.EmptyWorkObjectBody() blockBody.SetHeader(blockHeader) - blockWoHeader := types.NewWorkObjectHeader(blockHeader.Hash(), types.EmptyHash, big.NewInt(int64(i+1)), big.NewInt(3000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 2, common.Location{0, byte(j)}, common.BytesToAddress([]byte{1}, common.Location{0, byte(j)})) + blockWoHeader := types.NewWorkObjectHeader(blockHeader.Hash(), types.EmptyHash, big.NewInt(int64(i+1)), big.NewInt(3000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 2, common.Location{0, byte(j)}, common.BytesToAddress([]byte{1}, common.Location{0, byte(j)}), []byte{0, 1, 2, 3}) block := types.NewWorkObject(blockWoHeader, blockBody, nil) WriteWorkObject(db, block.Hash(), block, types.BlockObject, common.ZONE_CTX) hashes[i][block.Hash()] = true @@ -345,7 +345,7 @@ func TestCommonAncestor(t *testing.T) { regionHeader.SetNumber(big.NewInt(1), common.REGION_CTX) regionBody := types.EmptyWorkObjectBody() regionBody.SetHeader(regionHeader) - regionWoHeader := types.NewWorkObjectHeader(regionHeader.Hash(), types.EmptyRootHash, big.NewInt(1), big.NewInt(3000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 1, common.Location{0}, common.BytesToAddress([]byte{0}, common.Location{0, 0})) + regionWoHeader := types.NewWorkObjectHeader(regionHeader.Hash(), types.EmptyRootHash, big.NewInt(1), big.NewInt(3000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 1, common.Location{0}, common.BytesToAddress([]byte{0}, common.Location{0, 0}), []byte{0, 1, 2, 3}) regionBlock := types.NewWorkObject(regionWoHeader, regionBody, nil) WriteWorkObject(db, regionBlock.Hash(), regionBlock, types.BlockObject, common.REGION_CTX) @@ -353,7 +353,7 @@ func TestCommonAncestor(t *testing.T) { zone0Header := types.EmptyHeader() zone0Body := types.EmptyWorkObjectBody() zone0Body.SetHeader(zone0Header) - zone0WoHeader := types.NewWorkObjectHeader(zone0Header.Hash(), regionBlock.Hash(), big.NewInt(2), big.NewInt(3000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 2, common.Location{0, 0}, common.BytesToAddress([]byte{0}, common.Location{0, 0})) + zone0WoHeader := types.NewWorkObjectHeader(zone0Header.Hash(), regionBlock.Hash(), big.NewInt(2), big.NewInt(3000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 2, common.Location{0, 0}, common.BytesToAddress([]byte{0}, common.Location{0, 0}), []byte{0, 1, 2, 3}) zone0Block := types.NewWorkObject(zone0WoHeader, zone0Body, nil) WriteWorkObject(db, zone0Block.Hash(), zone0Block, types.BlockObject, common.ZONE_CTX) @@ -361,14 +361,14 @@ func TestCommonAncestor(t *testing.T) { zone1Header1 := types.EmptyHeader() zone1Body1 := types.EmptyWorkObjectBody() zone1Body1.SetHeader(zone1Header1) - zone1WoHeader1 := types.NewWorkObjectHeader(zone1Header1.Hash(), regionBlock.Hash(), big.NewInt(2), big.NewInt(3000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 2, common.Location{0, 1}, common.BytesToAddress([]byte{0}, common.Location{0, 1})) + zone1WoHeader1 := types.NewWorkObjectHeader(zone1Header1.Hash(), regionBlock.Hash(), big.NewInt(2), big.NewInt(3000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 2, common.Location{0, 1}, common.BytesToAddress([]byte{0}, common.Location{0, 1}), []byte{0, 1, 2, 3}) zone1Block1 := types.NewWorkObject(zone1WoHeader1, zone1Body1, nil) WriteWorkObject(db, zone1Block1.Hash(), zone1Block1, types.BlockObject, common.ZONE_CTX) zone1Header2 := types.EmptyHeader() zone1Body2 := types.EmptyWorkObjectBody() zone1Body2.SetHeader(zone1Header2) - zone1WoHeader2 := types.NewWorkObjectHeader(zone1Header2.Hash(), zone1Block1.Hash(), big.NewInt(3), big.NewInt(3000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 3, common.Location{0, 1}, common.BytesToAddress([]byte{0}, common.Location{0, 1})) + zone1WoHeader2 := types.NewWorkObjectHeader(zone1Header2.Hash(), zone1Block1.Hash(), big.NewInt(3), big.NewInt(3000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 3, common.Location{0, 1}, common.BytesToAddress([]byte{0}, common.Location{0, 1}), []byte{0, 1, 2, 3}) zone1Block2 := types.NewWorkObject(zone1WoHeader2, zone1Body2, nil) WriteWorkObject(db, zone1Block2.Hash(), zone1Block2, types.BlockObject, common.ZONE_CTX) @@ -588,7 +588,7 @@ func createTestWorkObject() *types.WorkObject { woBody := types.EmptyWorkObjectBody() woBody.SetHeader(types.EmptyHeader()) - woHeader := types.NewWorkObjectHeader(types.EmptyRootHash, types.EmptyRootHash, big.NewInt(11), big.NewInt(30000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 1, common.LocationFromAddressBytes([]byte{0x01, 0x01}), common.BytesToAddress([]byte{0}, common.Location{0, 0})) + woHeader := types.NewWorkObjectHeader(types.EmptyRootHash, types.EmptyRootHash, big.NewInt(11), big.NewInt(30000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 1, common.LocationFromAddressBytes([]byte{0x01, 0x01}), common.BytesToAddress([]byte{0}, common.Location{0, 0}), []byte{0, 1, 2, 3}) return types.NewWorkObject(woHeader, woBody, nil) } @@ -742,7 +742,7 @@ func createBlockWithTransactions(txs types.Transactions) *types.WorkObject { woBody := types.EmptyWorkObjectBody() woBody.SetHeader(types.EmptyHeader()) woBody.SetTransactions(txs) - return types.NewWorkObject(types.NewWorkObjectHeader(types.EmptyRootHash, types.EmptyRootHash, big.NewInt(0), big.NewInt(30000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 1, common.LocationFromAddressBytes([]byte{0x01, 0x01}), common.BytesToAddress([]byte{0}, common.Location{0, 0})), woBody, nil) + return types.NewWorkObject(types.NewWorkObjectHeader(types.EmptyRootHash, types.EmptyRootHash, big.NewInt(0), big.NewInt(30000), big.NewInt(42), types.EmptyRootHash, types.BlockNonce{23}, 1, 1, common.LocationFromAddressBytes([]byte{0x01, 0x01}), common.BytesToAddress([]byte{0}, common.Location{0, 0}), []byte{0, 1, 2, 3}), woBody, nil) } func writeBlockForReceipts(db ethdb.Database, hash common.Hash, txs types.Transactions) { diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 8a89753ebc..907b1763f5 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -113,7 +113,10 @@ var ( configPrefix = []byte("quai-config-") // config prefix for the db // Chain index prefixes (use `i` + single byte to avoid mixing data types). - BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress + BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress + CoinbaseLockupPrefix = []byte("cl") // coinbaseLockupPrefix + ownerContract + beneficiaryMiner + lockupByte + epoch -> lockup + createdCoinbaseLockupsPrefix = []byte("ccl") // createdCoinbaseLockupsPrefix + hash -> [][]byte + deletedCoinbaseLockupsPrefix = []byte("dcl") // deletedCoinbaseLockupsPrefix + hash -> [][]byte ) const ( @@ -387,3 +390,58 @@ func utxoToBlockHeightKey(txHash common.Hash, index uint16) []byte { txHash[common.HashLength-2] = indexBytes[1] return append(utxoToBlockHeightPrefix, txHash[:]...) } + +var CoinbaseLockupKeyLength = len(CoinbaseLockupPrefix) + 2*common.AddressLength + 1 + 4 + +func CoinbaseLockupKey(ownerContract common.Address, beneficiaryMiner common.Address, lockupByte byte, epoch uint32) []byte { + epochBytes := make([]byte, 4) + binary.BigEndian.PutUint32(epochBytes, epoch) + ownerBytes := ownerContract.Bytes() + beneficiaryBytes := beneficiaryMiner.Bytes() + combined := append(ownerBytes, beneficiaryBytes...) + combined = append(combined, lockupByte) + combined = append(combined, epochBytes...) + return append(CoinbaseLockupPrefix, combined...) +} + +func createdCoinbaseLockupsKey(hash common.Hash) []byte { + return append(createdCoinbaseLockupsPrefix, hash.Bytes()...) +} + +func deletedCoinbaseLockupsKey(hash common.Hash) []byte { + return append(deletedCoinbaseLockupsPrefix, hash.Bytes()...) +} + +func ReverseCoinbaseLockupKey(data []byte, location common.Location) (common.Address, common.Address, byte, uint32, error) { + + epochLength := 4 // Length of the epoch in bytes + prefixLength := len(CoinbaseLockupPrefix) + + // Ensure the data is long enough to contain all components + if len(data) < prefixLength+2*common.AddressLength+1+epochLength { + return common.Address{}, common.Address{}, 0, 0, fmt.Errorf("key is too short to parse") + } + + // Check and remove the prefix + if !bytes.HasPrefix(data, CoinbaseLockupPrefix) { + return common.Address{}, common.Address{}, 0, 0, fmt.Errorf("key does not have the correct prefix") + } + data = data[prefixLength:] // Remove the prefix + + // Extract the owner contract address + ownerContract := common.BytesToAddress(data[:common.AddressLength], location) + data = data[common.AddressLength:] // Advance the slice + + // Extract the beneficiary miner address + beneficiaryMiner := common.BytesToAddress(data[:common.AddressLength], location) + data = data[common.AddressLength:] // Advance the slice + + // Extract the lockup byte + lockupByte := data[0] + data = data[1:] // Advance the slice + + // Extract the epoch + epoch := binary.BigEndian.Uint32(data[:epochLength]) + + return ownerContract, beneficiaryMiner, lockupByte, epoch, nil +} diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 30219f3876..cafcae9b35 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -241,6 +241,12 @@ func (b *tableBatch) Replay(w ethdb.KeyValueWriter) error { return b.batch.Replay(&tableReplayer{w: w, prefix: b.prefix}) } +func (b *tableBatch) SetPending(pending bool) {} + +func (b *tableBatch) GetPending(key []byte) (bool, []byte) { + return false, nil +} + // tableIterator is a wrapper around a database iterator that prefixes each key access // with a pre-configured string. type tableIterator struct { diff --git a/core/slice.go b/core/slice.go index 87384b051b..28c0c23789 100644 --- a/core/slice.go +++ b/core/slice.go @@ -924,6 +924,7 @@ func (sl *Slice) combinePendingHeader(header *types.WorkObject, slPendingHeader combinedPendingHeader.WorkObjectHeader().SetPrimeTerminusNumber(header.PrimeTerminusNumber()) combinedPendingHeader.WorkObjectHeader().SetLock(header.Lock()) combinedPendingHeader.WorkObjectHeader().SetPrimaryCoinbase(header.PrimaryCoinbase()) + combinedPendingHeader.WorkObjectHeader().SetData(header.Data()) combinedPendingHeader.Header().SetEtxRollupHash(header.EtxRollupHash()) combinedPendingHeader.Header().SetUncledEntropy(header.Header().UncledEntropy()) diff --git a/core/state/statedb.go b/core/state/statedb.go index 8918f136b5..b74d90c569 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -32,6 +32,7 @@ import ( "github.com/dominant-strategies/go-quai/core/state/snapshot" "github.com/dominant-strategies/go-quai/core/types" "github.com/dominant-strategies/go-quai/crypto" + "github.com/dominant-strategies/go-quai/ethdb" "github.com/dominant-strategies/go-quai/log" "github.com/dominant-strategies/go-quai/metrics_config" "github.com/dominant-strategies/go-quai/rlp" @@ -45,10 +46,10 @@ type revision struct { var ( // emptyRoot is the known root hash of an empty trie. - emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") - newestEtxKey = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff") // max hash - oldestEtxKey = common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffe") // max hash - 1 - + emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + newestEtxKey = common.HexToHash("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff") // max hash + oldestEtxKey = common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffe") // max hash - 1 + currentEpochKey = common.HexToHash("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffd") // max hash - 2 ) type proofList [][]byte @@ -349,6 +350,7 @@ func (s *StateDB) TxIndex() int { } func (s *StateDB) GetCode(addr common.InternalAddress) []byte { + return []byte{} stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.Code(s.db) @@ -422,6 +424,10 @@ func (s *StateDB) ETXDatabase() Database { return s.etxDb } +func (s *StateDB) UnderlyingDatabase() ethdb.KeyValueReader { + return s.db.TrieDB().DiskDB() +} + // StorageTrie returns the storage trie of an account. // The return value is a copy and is nil for non-existent accounts. func (s *StateDB) StorageTrie(addr common.InternalAddress) Trie { @@ -702,6 +708,30 @@ func (s *StateDB) CommitEtxs() (common.Hash, error) { return root, err } +func (s *StateDB) GetLatestEpoch() (uint32, error) { + epochBytes, err := s.etxTrie.TryGet(currentEpochKey[:]) + if err != nil { + return 0, err + } + if epochBytes == nil || len(epochBytes) == 0 { + return 0, nil + } + if len(epochBytes) != 4 { + return 0, fmt.Errorf("invalid epoch length: %d", len(epochBytes)) + } + epoch := binary.BigEndian.Uint32(epochBytes) + return epoch, nil +} + +func (s *StateDB) SetLatestEpoch(epoch uint32) error { + epochBytes := make([]byte, 4) + binary.BigEndian.PutUint32(epochBytes, epoch) + if err := s.etxTrie.TryUpdate(currentEpochKey[:], epochBytes); err != nil { + return err + } + return nil +} + // getDeletedStateObject is similar to getStateObject, but instead of returning // nil for a deleted state object, it returns the actual object with the deleted // flag set. This is needed by the state journal to revert to the correct s- diff --git a/core/state_processor.go b/core/state_processor.go index c6c2fc096c..27571168bc 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -206,10 +206,14 @@ func NewStateProcessor(config *params.ChainConfig, hc *HeaderChain, engine conse } type UtxosCreatedDeleted struct { - UtxosCreatedKeys [][]byte - UtxosCreatedHashes []common.Hash - UtxosDeleted []*types.SpentUtxoEntry - UtxosDeletedHashes []common.Hash + UtxosCreatedKeys [][]byte + UtxosCreatedHashes []common.Hash + UtxosDeleted []*types.SpentUtxoEntry + UtxosDeletedHashes []common.Hash + CoinbaseLockupsCreatedHashes map[string]common.Hash + CoinbaseLockupsDeletedHashes map[string]common.Hash + CoinbaseLockupsDeleted map[[47]byte][]byte + RotatedEpochs map[string]struct{} } // Process processes the state changes according to the Quai rules by running @@ -240,6 +244,9 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty } time1 := common.PrettyDuration(time.Since(start)) + // enable the batch pending cache + batch.SetPending(true) + parentEvmRoot := parent.Header().EVMRoot() parentEtxSetRoot := parent.Header().EtxSetRoot() parentQuaiStateSize := parent.QuaiStateSize() @@ -257,6 +264,10 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty return types.Receipts{}, []*types.Transaction{}, []*types.Log{}, nil, 0, 0, 0, nil, nil, err } utxosCreatedDeleted := new(UtxosCreatedDeleted) // utxos created and deleted in this block + utxosCreatedDeleted.CoinbaseLockupsCreatedHashes = make(map[string]common.Hash) + utxosCreatedDeleted.CoinbaseLockupsDeletedHashes = make(map[string]common.Hash) + utxosCreatedDeleted.CoinbaseLockupsDeleted = make(map[[47]byte][]byte) + utxosCreatedDeleted.RotatedEpochs = make(map[string]struct{}) // Apply the previous inbound ETXs to the ETX set state prevInboundEtxs := rawdb.ReadInboundEtxs(p.hc.bc.db, header.ParentHash(nodeCtx)) if len(prevInboundEtxs) > 0 { @@ -266,6 +277,15 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty } time2 := common.PrettyDuration(time.Since(start)) + coinbaseLockupEpoch, err := statedb.GetLatestEpoch() + if err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("could not get latest epoch: %w", err) + } + if blockNumber.Uint64()%params.CoinbaseEpochBlocks == 0 || coinbaseLockupEpoch == 0 { + coinbaseLockupEpoch++ + statedb.SetLatestEpoch(coinbaseLockupEpoch) + } + var timeSign, timePrepare, timeQiToQuai, timeQuaiToQi, timeCoinbase, timeEtx, timeTx time.Duration startTimeSenders := time.Now() senders := make(map[common.Hash]*common.InternalAddress) // temporary cache for senders of internal txs @@ -293,7 +313,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty if err != nil { return nil, nil, nil, nil, 0, 0, 0, nil, nil, err } - vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, p.vmConfig) + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, p.vmConfig, batch) time3 := common.PrettyDuration(time.Since(start)) // Iterate over and process the individual transactions. @@ -336,7 +356,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty } // Redeem all Quai for the different lock up periods - err, unlocks := RedeemLockedQuai(p.hc, header, parent, statedb) + unlocks, err := RedeemLockedQuai(p.hc, header, parent, statedb, vmenv) if err != nil { return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("error redeeming locked quai: %w", err) } @@ -418,7 +438,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty timePrepare += timePrepareDelta var receipt *types.Receipt - var addReceipt bool + if tx.Type() == types.ExternalTxType { etxCount++ startTimeEtx := time.Now() @@ -434,6 +454,81 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("invalid external transaction: etx %x is not in order or not found in unspent etx set", tx.Hash()) } + if etx.EtxType() == types.CoinbaseLockupType { + // This is either an unlocked Qi coinbase that was redeemed or Wrapped Qi + // An unlocked/redeemed Quai coinbase ETX is processed below as a standard Quai ETX + if tx.To().IsInQiLedgerScope() { + txGas := tx.Gas() + denominations := misc.FindMinDenominations(etx.Value()) + total := big.NewInt(0) + outputIndex := uint16(0) + success := true + // Iterate over the denominations in descending order + for denomination := types.MaxDenomination; denomination >= 0; denomination-- { + // If the denomination count is zero, skip it + if denominations[uint8(denomination)] == 0 { + continue + } + + for j := uint64(0); j < denominations[uint8(denomination)]; j++ { + if txGas < params.CallValueTransferGas || outputIndex >= types.MaxOutputIndex { + // No more gas, the rest of the denominations are lost but the tx is still valid + success = false + break + } + txGas -= params.CallValueTransferGas + if err := gp.SubGas(params.CallValueTransferGas); err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, err + } + *usedGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is + totalEtxGas += params.CallValueTransferGas // In the future we may want to determine what a fair gas cost is + utxo := types.NewUtxoEntry(types.NewTxOut(uint8(denomination), etx.To().Bytes(), big.NewInt(0))) + // the ETX hash is guaranteed to be unique + if err := rawdb.CreateUTXO(batch, etx.Hash(), outputIndex, utxo); err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, err + } + utxosCreatedDeleted.UtxosCreatedHashes = append(utxosCreatedDeleted.UtxosCreatedHashes, types.UTXOHash(etx.Hash(), outputIndex, utxo)) + utxosCreatedDeleted.UtxosCreatedKeys = append(utxosCreatedDeleted.UtxosCreatedKeys, rawdb.UtxoKeyWithDenomination(etx.Hash(), outputIndex, utxo.Denomination)) + p.logger.Debugf("Converting Quai to Qi %032x with denomination %d index %d lock %d\n", tx.Hash(), denomination, outputIndex, 0) + total.Add(total, types.Denominations[uint8(denomination)]) + outputIndex++ + } + } + receipt := &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: etx.Gas() - txGas, TxHash: tx.Hash(), + Logs: []*types.Log{{ + Address: *etx.To(), + Topics: []common.Hash{types.QuaiToQiConversionTopic}, + Data: total.Bytes(), + }}, + } + + if !success { + receipt.Status = types.ReceiptStatusFailed + receipt.GasUsed = etx.Gas() + } + receipts = append(receipts, receipt) + allLogs = append(allLogs, receipt.Logs...) + continue + } + } else if etx.EtxType() == types.WrappingQiType { + if len(etx.Data()) != common.AddressLength { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("wrapping Qi ETX %x has invalid data length", etx.Hash()) + } + if etx.To() == nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("wrapping Qi ETX %x has no recipient", etx.Hash()) + } + ownerContractAddr := common.BytesToAddress(etx.Data(), nodeLocation) + if err := vm.WrapQi(statedb, ownerContractAddr, *etx.To(), common.OneInternal(nodeLocation), etx.Value(), nodeLocation); err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("could not wrap Qi: %v", err) + } + if err := gp.SubGas(params.QiToQuaiConversionGas); err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, err + } + *usedGas += params.QiToQuaiConversionGas + totalEtxGas += params.QiToQuaiConversionGas + continue + } + // check if the tx is a coinbase tx // coinbase tx // 1) is a external tx type @@ -451,50 +546,158 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("coinbase tx %x has invalid recipient: %w", tx.Hash(), err) } lockupByte := tx.Data()[0] - if tx.To().IsInQiLedgerScope() { - if int(lockupByte) > len(params.LockupByteToBlockDepth)-1 { - return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("coinbase lockup byte %d is out of range", lockupByte) + if int(lockupByte) > len(params.LockupByteToBlockDepth)-1 { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("coinbase lockup byte %d is out of range", lockupByte) + } + if tx.To().IsInQiLedgerScope() { // Qi coinbase + _, err := tx.To().InternalAndQiAddress() + if err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("coinbase tx %x has invalid recipient: %w", tx.Hash(), err) } - var lockup *big.Int - // The first lock up period changes after the fork - if lockupByte == 0 { - if block.NumberU64(common.ZONE_CTX) < params.GoldenAgeForkNumberV1 { - lockup = new(big.Int).SetUint64(params.OldConversionLockPeriod) + total := big.NewInt(0) + lockup := new(big.Int).SetUint64(params.LockupByteToBlockDepth[lockupByte]) + if lockup.Uint64() < params.OldConversionLockPeriod { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("coinbase lockup period is less than the minimum lockup period of %d blocks", params.OldConversionLockPeriod) + } + lockup.Add(lockup, blockNumber) + value := params.CalculateCoinbaseValueWithLockup(tx.Value(), lockupByte) + if len(tx.Data()) == 1 { + denominations := misc.FindMinDenominations(value) + outputIndex := uint16(0) + // Iterate over the denominations in descending order + for denomination := types.MaxDenomination; denomination >= 0; denomination-- { + // If the denomination count is zero, skip it + if denominations[uint8(denomination)] == 0 { + continue + } + for j := uint64(0); j < denominations[uint8(denomination)]; j++ { + if outputIndex >= types.MaxOutputIndex { + // No more gas, the rest of the denominations are lost but the tx is still valid + break + } + utxo := types.NewUtxoEntry(types.NewTxOut(uint8(denomination), tx.To().Bytes(), lockup)) + // the ETX hash is guaranteed to be unique + if err := rawdb.CreateUTXO(batch, etx.Hash(), outputIndex, utxo); err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, err + } + utxosCreatedDeleted.UtxosCreatedHashes = append(utxosCreatedDeleted.UtxosCreatedHashes, types.UTXOHash(etx.Hash(), outputIndex, utxo)) + utxosCreatedDeleted.UtxosCreatedKeys = append(utxosCreatedDeleted.UtxosCreatedKeys, rawdb.UtxoKeyWithDenomination(etx.Hash(), outputIndex, utxo.Denomination)) + p.logger.Debugf("Creating UTXO for coinbase %032x with denomination %d index %d\n", tx.Hash(), denomination, outputIndex) + total.Add(total, types.Denominations[uint8(denomination)]) + outputIndex++ + } + } + receipt = &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: 0, TxHash: tx.Hash()} + } else if len(tx.Data()) == common.AddressLength+1 || len(tx.Data()) == common.AddressLength+common.AddressLength+1 { + contractAddr := common.BytesToAddress(tx.Data()[1:common.AddressLength+1], nodeLocation) + internal, err := tx.To().InternalAndQiAddress() + if err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("coinbase tx %x has invalid recipient: %w", tx.Hash(), err) + } + if statedb.GetCode(internal) == nil { + // No code at contract address + // Coinbase reward is lost + // Justification: We should not store a coinbase lockup that can never be claimed + receipt = &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusFailed, GasUsed: 0, TxHash: tx.Hash()} } else { - lockup = new(big.Int).SetUint64(params.NewConversionLockPeriod) + var delegate common.Address + if len(tx.Data()) == common.AddressLength+common.AddressLength+1 { + delegate = common.BytesToAddress(tx.Data()[common.AddressLength+1:], nodeLocation) + } else { + delegate = common.Zero + } + oldCoinbaseLockupKey, newCoinbaseLockupKey, oldCoinbaseLockupHash, newCoinbaseLockupHash, err := vm.AddNewLock(statedb, batch, contractAddr, *etx.To(), delegate, common.OneInternal(nodeLocation), lockupByte, lockup.Uint64(), coinbaseLockupEpoch, value, nodeLocation, true) + if err != nil || newCoinbaseLockupHash == nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("could not add new lock: %w", err) + } + // Store the new lockup key every time + utxosCreatedDeleted.CoinbaseLockupsCreatedHashes[string(newCoinbaseLockupKey)] = *newCoinbaseLockupHash + + if oldCoinbaseLockupHash != nil { + // We deleted (updated) the old lockup, write it to deleted list but only the first time + if _, exists := utxosCreatedDeleted.CoinbaseLockupsDeletedHashes[string(oldCoinbaseLockupKey)]; !exists { + if _, exists := utxosCreatedDeleted.RotatedEpochs[string(newCoinbaseLockupKey)]; !exists { + // We only want to add a delete if we have not rotated the epoch (we haven't created a new lock) because otherwise there is nothing to delete + utxosCreatedDeleted.CoinbaseLockupsDeletedHashes[string(oldCoinbaseLockupKey)] = *oldCoinbaseLockupHash + utxosCreatedDeleted.UtxosDeletedHashes = append(utxosCreatedDeleted.UtxosDeletedHashes, *oldCoinbaseLockupHash) + } + } + } else { + // If we did not delete, we are rotating the epoch and need to store it + utxosCreatedDeleted.RotatedEpochs[string(newCoinbaseLockupKey)] = struct{}{} + } + receipt = &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: 0, TxHash: tx.Hash()} // todo: consider adding the reward to the receipt in a log } } else { - lockup = new(big.Int).SetUint64(params.LockupByteToBlockDepth[lockupByte]) + // Coinbase data is either too long or too small + // Coinbase reward is lost + receipt = &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusFailed, GasUsed: 0, TxHash: tx.Hash()} + } + receipts = append(receipts, receipt) + allLogs = append(allLogs, receipt.Logs...) + } else if tx.To().IsInQuaiLedgerScope() { // Quai coinbase + _, err := tx.To().InternalAndQuaiAddress() + if err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("coinbase tx %x has invalid recipient: %w", tx.Hash(), err) + } + if len(tx.Data()) == 1 { + // Coinbase is valid, no gas used + receipt = &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: 0, TxHash: tx.Hash()} + } else if len(tx.Data()) != common.AddressLength+1 && len(tx.Data()) != common.AddressLength+common.AddressLength+1 { + // Coinbase data is either too long or too small + // Coinbase reward is lost + receipt = &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusFailed, GasUsed: 0, TxHash: tx.Hash()} + } else { // Quai coinbase lockup contract + // Create params for uint256 lockup, uint256 balance, address recipient + lockup := new(big.Int).SetUint64(params.LockupByteToBlockDepth[lockupByte]) if lockup.Uint64() < params.OldConversionLockPeriod { return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("coinbase lockup period is less than the minimum lockup period of %d blocks", params.OldConversionLockPeriod) } - } - lockup.Add(lockup, blockNumber) - value := params.CalculateCoinbaseValueWithLockup(tx.Value(), lockupByte) - denominations := misc.FindMinDenominations(value) - outputIndex := uint16(0) - // Iterate over the denominations in descending order - for denomination := types.MaxDenomination; denomination >= 0; denomination-- { - // If the denomination count is zero, skip it - if denominations[uint8(denomination)] == 0 { - continue + lockup.Add(lockup, blockNumber) + + contractAddr := common.BytesToAddress(tx.Data()[1:common.AddressLength+1], nodeLocation) + internal, err := contractAddr.InternalAndQuaiAddress() + if err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("coinbase tx %x has invalid recipient: %w", tx.Hash(), err) } - for j := uint64(0); j < denominations[uint8(denomination)]; j++ { - if outputIndex >= types.MaxOutputIndex { - // No more gas, the rest of the denominations are lost but the tx is still valid - break + if statedb.GetCode(internal) == nil { + // No code at contract address + // Coinbase reward is lost + // Justification: We should not store a coinbase lockup that can never be claimed + receipt = &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusFailed, GasUsed: 0, TxHash: tx.Hash()} + } else { + var delegate common.Address + if len(tx.Data()) == common.AddressLength+common.AddressLength+1 { + delegate = common.BytesToAddress(tx.Data()[common.AddressLength+1:], nodeLocation) + } else { + delegate = common.Zero } - utxo := types.NewUtxoEntry(types.NewTxOut(uint8(denomination), tx.To().Bytes(), lockup)) - // the ETX hash is guaranteed to be unique - if err := rawdb.CreateUTXO(batch, etx.Hash(), outputIndex, utxo); err != nil { - return nil, nil, nil, nil, 0, 0, 0, nil, nil, err + reward := params.CalculateCoinbaseValueWithLockup(tx.Value(), lockupByte) + // Add the lockup owned by the smart contract with the miner as beneficiary + oldCoinbaseLockupKey, newCoinbaseLockupKey, oldCoinbaseLockupHash, newCoinbaseLockupHash, err := vm.AddNewLock(statedb, batch, contractAddr, *etx.To(), delegate, common.OneInternal(nodeLocation), lockupByte, lockup.Uint64(), coinbaseLockupEpoch, reward, nodeLocation, true) + if err != nil || newCoinbaseLockupHash == nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("could not add new lock: %w", err) } - utxosCreatedDeleted.UtxosCreatedHashes = append(utxosCreatedDeleted.UtxosCreatedHashes, types.UTXOHash(etx.Hash(), outputIndex, utxo)) - utxosCreatedDeleted.UtxosCreatedKeys = append(utxosCreatedDeleted.UtxosCreatedKeys, rawdb.UtxoKeyWithDenomination(etx.Hash(), outputIndex, utxo.Denomination)) - p.logger.Debugf("Creating UTXO for coinbase %032x with denomination %d index %d\n", tx.Hash(), denomination, outputIndex) - outputIndex++ + // Store the new lockup key every time + utxosCreatedDeleted.CoinbaseLockupsCreatedHashes[string(newCoinbaseLockupKey)] = *newCoinbaseLockupHash + + if oldCoinbaseLockupHash != nil { + // We deleted (updated) the old lockup, write it to deleted list but only the first time + if _, exists := utxosCreatedDeleted.CoinbaseLockupsDeletedHashes[string(oldCoinbaseLockupKey)]; !exists { + if _, exists := utxosCreatedDeleted.RotatedEpochs[string(newCoinbaseLockupKey)]; !exists { // Don't register deletes for any rotated epochs + utxosCreatedDeleted.CoinbaseLockupsDeletedHashes[string(oldCoinbaseLockupKey)] = *oldCoinbaseLockupHash + utxosCreatedDeleted.UtxosDeletedHashes = append(utxosCreatedDeleted.UtxosDeletedHashes, *oldCoinbaseLockupHash) + } + } + } else { + // If we did not delete, we are rotating the epoch and need to store it + utxosCreatedDeleted.RotatedEpochs[string(newCoinbaseLockupKey)] = struct{}{} + } + receipt = &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: 0, TxHash: tx.Hash()} } } + receipts = append(receipts, receipt) + allLogs = append(allLogs, receipt.Logs...) } if block.NumberU64(common.ZONE_CTX) > params.TimeToStartTx { // subtract the minimum tx gas from the gas pool @@ -507,8 +710,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty timeDelta := time.Since(startTimeEtx) timeCoinbase += timeDelta continue - } - if etx.To().IsInQiLedgerScope() { + } else if !types.IsCoinBaseTx(tx) && etx.To().IsInQiLedgerScope() { if etx.ETXSender().Location().Equal(*etx.To().Location()) { // Quai->Qi Conversion var lockup *big.Int if block.NumberU64(common.ZONE_CTX) < params.GoldenAgeForkNumberV1 { @@ -520,7 +722,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty value := etx.Value() txGas := etx.Gas() if txGas < params.TxGas { - continue + return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("etx gas is less than the minimum gas required") } txGas -= params.TxGas if err := gp.SubGas(params.TxGas); err != nil { @@ -530,15 +732,19 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty totalEtxGas += params.TxGas denominations := misc.FindMinDenominations(value) outputIndex := uint16(0) + total := big.NewInt(0) + success := true // Iterate over the denominations in descending order for denomination := types.MaxDenomination; denomination >= 0; denomination-- { // If the denomination count is zero, skip it if denominations[uint8(denomination)] == 0 { continue } + for j := uint64(0); j < denominations[uint8(denomination)]; j++ { if txGas < params.CallValueTransferGas || outputIndex >= types.MaxOutputIndex { // No more gas, the rest of the denominations are lost but the tx is still valid + success = false break } txGas -= params.CallValueTransferGas @@ -555,10 +761,24 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty utxosCreatedDeleted.UtxosCreatedHashes = append(utxosCreatedDeleted.UtxosCreatedHashes, types.UTXOHash(etx.Hash(), outputIndex, utxo)) utxosCreatedDeleted.UtxosCreatedKeys = append(utxosCreatedDeleted.UtxosCreatedKeys, rawdb.UtxoKeyWithDenomination(etx.Hash(), outputIndex, utxo.Denomination)) p.logger.Debugf("Converting Quai to Qi %032x with denomination %d index %d lock %d\n", tx.Hash(), denomination, outputIndex, lock) + total.Add(total, types.Denominations[uint8(denomination)]) outputIndex++ } } - } else { + receipt := &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: etx.Gas() - txGas, TxHash: tx.Hash(), + Logs: []*types.Log{{ + Address: *etx.To(), + Topics: []common.Hash{types.QuaiToQiConversionTopic}, + Data: total.Bytes(), + }}, + } + if !success { + receipt.Status = types.ReceiptStatusFailed + receipt.GasUsed = etx.Gas() + } + receipts = append(receipts, receipt) + allLogs = append(allLogs, receipt.Logs...) + } else if !types.IsCoinBaseTx(tx) && !etx.ETXSender().Location().Equal(*etx.To().Location()) && etx.To().IsInQiLedgerScope() { // Regular Qi ETX utxo := types.NewUtxoEntry(types.NewTxOut(uint8(etx.Value().Uint64()), etx.To().Bytes(), big.NewInt(0))) // There are no more checks to be made as the ETX is worked so add it to the set if err := rawdb.CreateUTXO(batch, etx.OriginatingTxHash(), etx.ETXIndex(), utxo); err != nil { @@ -586,6 +806,8 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty totalEtxGas += params.QiToQuaiConversionGas continue // locked and redeemed later } + // Apply ETX to Quai state + // This could also be an unlocked Quai coinbase redemption ETX, the process is the same fees := big.NewInt(0) prevZeroBal := prepareApplyETX(statedb, msg.Value(), nodeLocation) receipt, fees, err = applyTransaction(msg, parent, p.config, p.hc, gp, statedb, blockNumber, blockHash, etx, usedGas, usedState, vmenv, &etxRLimit, &etxPLimit, p.logger) @@ -593,15 +815,28 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty if err != nil { return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } - addReceipt = true quaiFees.Add(quaiFees, fees) totalEtxGas += receipt.GasUsed timeDelta := time.Since(startTimeEtx) timeQiToQuai += timeDelta + if receipt.Status == types.ReceiptStatusSuccessful { + for _, etx := range receipt.OutboundEtxs { + emittedEtxs = append(emittedEtxs, etx) + } + for _, hash := range receipt.CoinbaseLockupDeletedHashes { + utxosCreatedDeleted.UtxosDeletedHashes = append(utxosCreatedDeleted.UtxosDeletedHashes, *hash) + } + for key, lockup := range receipt.CoinbaseLockupsDeleted { + utxosCreatedDeleted.CoinbaseLockupsDeleted[key] = lockup + } + } + receipts = append(receipts, receipt) + allLogs = append(allLogs, receipt.Logs...) + i++ } - } else if tx.Type() == types.QuaiTxType { + } else if tx.Type() == types.QuaiTxType { // Regular Quai tx startTimeTx := time.Now() fees := big.NewInt(0) @@ -609,7 +844,6 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty if err != nil { return nil, nil, nil, nil, 0, 0, 0, nil, nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } - addReceipt = true timeTxDelta := time.Since(startTimeTx) timeTx += timeTxDelta @@ -636,20 +870,23 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty minGasPrice = new(big.Int).Set(gasPrice) } } - - } else { - return nil, nil, nil, nil, 0, 0, 0, nil, nil, ErrTxTypeNotSupported - } - for _, etx := range receipt.OutboundEtxs { if receipt.Status == types.ReceiptStatusSuccessful { - emittedEtxs = append(emittedEtxs, etx) + for _, etx := range receipt.OutboundEtxs { + emittedEtxs = append(emittedEtxs, etx) + } + for _, hash := range receipt.CoinbaseLockupDeletedHashes { + utxosCreatedDeleted.UtxosDeletedHashes = append(utxosCreatedDeleted.UtxosDeletedHashes, *hash) + } + for key, lockup := range receipt.CoinbaseLockupsDeleted { + utxosCreatedDeleted.CoinbaseLockupsDeleted[key] = lockup + } } - } - if addReceipt { receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) + i++ + } else { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, ErrTxTypeNotSupported } - i++ } if nonEtxExists && block.BaseFee().Cmp(big.NewInt(0)) == 0 { @@ -700,19 +937,19 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty coinbaseReward := misc.CalculateReward(parent, block.WorkObjectHeader()) blockReward := new(big.Int).Add(coinbaseReward, quaiFees) - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(parentHash, nodeLocation), ETXIndex: uint16(len(emittedEtxs)), Sender: primaryCoinbase, Data: []byte{block.Lock()}}) + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(parentHash, nodeLocation), ETXIndex: uint16(len(emittedEtxs)), Sender: primaryCoinbase, Data: block.Data()}) emittedEtxs = append(emittedEtxs, coinbaseEtx) if qiFees.Cmp(big.NewInt(0)) != 0 { - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: qiFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(parentHash, nodeLocation), ETXIndex: uint16(len(emittedEtxs)), Sender: secondaryCoinbase, Data: []byte{block.Lock()}}) + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: qiFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(parentHash, nodeLocation), ETXIndex: uint16(len(emittedEtxs)), Sender: secondaryCoinbase, Data: block.Data()}) emittedEtxs = append(emittedEtxs, coinbaseEtx) } } else if bytes.Equal(block.PrimaryCoinbase().Bytes(), qiCoinbase.Bytes()) { coinbaseReward := misc.CalculateReward(parent, block.WorkObjectHeader()) blockReward := new(big.Int).Add(coinbaseReward, qiFees) - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(parentHash, nodeLocation), ETXIndex: uint16(len(emittedEtxs)), Sender: primaryCoinbase, Data: []byte{block.Lock()}}) + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(parentHash, nodeLocation), ETXIndex: uint16(len(emittedEtxs)), Sender: primaryCoinbase, Data: block.Data()}) emittedEtxs = append(emittedEtxs, coinbaseEtx) if quaiFees.Cmp(big.NewInt(0)) != 0 { - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: quaiFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(parentHash, nodeLocation), ETXIndex: uint16(len(emittedEtxs)), Sender: secondaryCoinbase, Data: []byte{block.Lock()}}) + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: quaiFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(parentHash, nodeLocation), ETXIndex: uint16(len(emittedEtxs)), Sender: secondaryCoinbase, Data: block.Data()}) emittedEtxs = append(emittedEtxs, coinbaseEtx) } } @@ -726,7 +963,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty } else { originHash = common.SetBlockHashForQi(parentHash, nodeLocation) } - emittedEtxs = append(emittedEtxs, types.NewTx(&types.ExternalTx{To: &uncleCoinbase, Gas: params.TxGas, Value: reward, EtxType: types.CoinbaseType, OriginatingTxHash: originHash, ETXIndex: uint16(len(emittedEtxs)), Sender: uncleCoinbase, Data: []byte{uncle.Lock()}})) + emittedEtxs = append(emittedEtxs, types.NewTx(&types.ExternalTx{To: &uncleCoinbase, Gas: params.TxGas, Value: reward, EtxType: types.CoinbaseType, OriginatingTxHash: originHash, ETXIndex: uint16(len(emittedEtxs)), Sender: uncleCoinbase, Data: uncle.Data()})) } updatedTokenChoiceSet, err := CalculateTokenChoicesSet(p.hc, parent, emittedEtxs) @@ -773,6 +1010,11 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty } } + for _, hash := range utxosCreatedDeleted.CoinbaseLockupsCreatedHashes { + // Update the created hash list with the latest new elements (instead of intermediate ones) + utxosCreatedDeleted.UtxosCreatedHashes = append(utxosCreatedDeleted.UtxosCreatedHashes, hash) + } + time4 := common.PrettyDuration(time.Since(start)) // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) multiSet, utxoSetSize, err := p.engine.Finalize(p.hc, batch, block, statedb, false, parentUtxoSetSize, utxosCreatedDeleted.UtxosCreatedHashes, utxosCreatedDeleted.UtxosDeletedHashes) @@ -823,6 +1065,16 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty if err := rawdb.WriteCreatedUTXOKeys(batch, blockHash, utxosCreatedDeleted.UtxosCreatedKeys); err != nil { // Could do this in Apply instead return nil, nil, nil, nil, 0, 0, 0, nil, nil, err } + coinbaseLockupsCreatedKeys := make([][]byte, 0, len(utxosCreatedDeleted.CoinbaseLockupsCreatedHashes)) + for key, _ := range utxosCreatedDeleted.CoinbaseLockupsCreatedHashes { + coinbaseLockupsCreatedKeys = append(coinbaseLockupsCreatedKeys, []byte(key)) + } + if err := rawdb.WriteCreatedCoinbaseLockupKeys(batch, blockHash, coinbaseLockupsCreatedKeys); err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, err + } + if err := rawdb.WriteDeletedCoinbaseLockups(batch, blockHash, utxosCreatedDeleted.CoinbaseLockupsDeleted); err != nil { + return nil, nil, nil, nil, 0, 0, 0, nil, nil, err + } return receipts, emittedEtxs, allLogs, statedb, *usedGas, *usedState, utxoSetSize, multiSet, unlocks, nil } @@ -830,7 +1082,7 @@ func (p *StateProcessor) Process(block *types.WorkObject, batch ethdb.Batch) (ty // It processes blocks based on predefined lockup periods and checks for unlockable Quai. // This function is intended to be run as part of the block processing. // Returns the list of unlocked coinbases -func RedeemLockedQuai(hc *HeaderChain, header *types.WorkObject, parent *types.WorkObject, statedb *state.StateDB) (error, []common.Unlock) { +func RedeemLockedQuai(hc *HeaderChain, header *types.WorkObject, parent *types.WorkObject, statedb *state.StateDB, vmenv *vm.EVM) ([]common.Unlock, error) { currentBlockHeight := header.Number(hc.NodeCtx()).Uint64() var blockDepths []uint64 @@ -884,50 +1136,47 @@ func RedeemLockedQuai(hc *HeaderChain, header *types.WorkObject, parent *types.W // Fetch the block at the calculated target height targetBlock := hc.GetBlockByNumber(targetBlockHeight) if targetBlock == nil { - return fmt.Errorf("block at height %d not found", targetBlockHeight), nil + return nil, fmt.Errorf("block at height %d not found", targetBlockHeight) } for _, etx := range targetBlock.Body().ExternalTransactions() { - // Check if the transaction is a conversion transaction - if types.IsCoinBaseTx(etx) && etx.ETXSender().IsInQuaiLedgerScope() { - // Redeem all unlocked Quai for the coinbase address - internal, err := etx.To().InternalAddress() - if err != nil { - return fmt.Errorf("error converting address to internal address: %v", err), nil - } - - lockupByte := etx.Data()[0] - // if lock up byte is 0, the fork change updates the lockup time - var lockup uint64 - if lockupByte == 0 { - lockup = params.OldConversionLockPeriod - if currentBlockHeight >= params.GoldenAgeForkNumberV1+params.NewConversionLockPeriod { - lockup = params.NewConversionLockPeriod + // Check if the transaction is a coinbase transaction + if types.IsCoinBaseTx(etx) && etx.To().IsInQuaiLedgerScope() { + + if len(etx.Data()) == 1 { + // Redeem all unlocked Quai for the coinbase address + internal, err := etx.To().InternalAddress() + if err != nil { + return nil, fmt.Errorf("error converting address to internal address: %v", err) } - } else { - lockup = params.LockupByteToBlockDepth[lockupByte] - } - if lockup == blockDepth { - balance := params.CalculateCoinbaseValueWithLockup(etx.Value(), lockupByte) - - if !statedb.Exist(internal) { - newAccountCreationGas := params.CallNewAccountGas(parent.QuaiStateSize()) - newAccountCreationFee := new(big.Int).Mul(new(big.Int).SetUint64(newAccountCreationGas), big.NewInt(params.InitialBaseFee)) - // Check if balance is greater than or equal to newAccountCreationFee - if balance.Cmp(newAccountCreationFee) >= 0 { - // If balance >= newAccountCreationFee, proceed with subtraction - balance.Sub(balance, newAccountCreationFee) - } else { - // Continue processing, user has not mined enough to pay for state fee - continue + + lockupByte := etx.Data()[0] + lockup := params.LockupByteToBlockDepth[lockupByte] + if lockup == blockDepth { + balance := params.CalculateCoinbaseValueWithLockup(etx.Value(), lockupByte) + + if !statedb.Exist(internal) { + newAccountCreationGas := params.CallNewAccountGas(parent.QuaiStateSize()) + newAccountCreationFee := new(big.Int).Mul(new(big.Int).SetUint64(newAccountCreationGas), big.NewInt(params.InitialBaseFee)) + // Check if balance is greater than or equal to newAccountCreationFee + if balance.Cmp(newAccountCreationFee) >= 0 { + // If balance >= newAccountCreationFee, proceed with subtraction + balance.Sub(balance, newAccountCreationFee) + } else { + // Continue processing, user has not mined enough to pay for state fee + continue + } } + hc.logger.Debugf("Redeeming %s locked Quai for %s at block depth %d", balance.String(), internal.Hex(), blockDepth) + statedb.AddBalance(internal, balance) + unlocks = append(unlocks, common.Unlock{ + Addr: internal, + Amt: balance, + }) } - hc.logger.Debugf("Redeeming %s locked Quai for %s at block depth %d", balance.String(), internal.Hex(), blockDepth) - statedb.AddBalance(internal, balance) - unlocks = append(unlocks, common.Unlock{ - Addr: internal, - Amt: balance, - }) + } else if len(etx.Data()) == common.AddressLength+1 || len(etx.Data()) == common.AddressLength+common.AddressLength+1 { + // This coinbase is owned by a smart contract and must be unlocked manually + continue } } @@ -965,7 +1214,7 @@ func RedeemLockedQuai(hc *HeaderChain, header *types.WorkObject, parent *types.W } } } - return nil, unlocks + return unlocks, nil } func applyTransaction(msg types.Message, parent *types.WorkObject, config *params.ChainConfig, bc ChainContext, gp *types.GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, usedState *uint64, evm *vm.EVM, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, *big.Int, error) { @@ -1010,9 +1259,10 @@ func applyTransaction(msg types.Message, parent *types.WorkObject, config *param // Create a new receipt for the transaction, storing the intermediate root and gas used // by the tx. - receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas, OutboundEtxs: result.Etxs} + receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas} if result.Failed() { receipt.Status = types.ReceiptStatusFailed + evm.UndoCoinbasesDeleted() logger.WithField("err", result.Err).Debug("Transaction failed") } else { receipt.Status = types.ReceiptStatusSuccessful @@ -1020,6 +1270,9 @@ func applyTransaction(msg types.Message, parent *types.WorkObject, config *param if result.ContractAddr != nil { receipt.ContractAddress = *result.ContractAddr } + receipt.OutboundEtxs = result.Etxs + receipt.CoinbaseLockupDeletedHashes = evm.CoinbaseDeletedHashes + receipt.CoinbaseLockupsDeleted = evm.CoinbasesDeleted } receipt.TxHash = tx.Hash() receipt.GasUsed = result.UsedGas @@ -1040,6 +1293,9 @@ func ValidateQiTxInputs(tx *types.Transaction, chain ChainContext, db ethdb.Read if tx.ChainId().Cmp(signer.ChainID()) != 0 { return nil, fmt.Errorf("tx %032x has wrong chain ID", tx.Hash()) } + if len(tx.Data()) != 0 && len(tx.Data()) != common.AddressLength { + return nil, fmt.Errorf("tx %v emits UTXO with invalid data length %d", tx.Hash().Hex(), len(tx.Data())) + } totalQitIn := big.NewInt(0) addresses := make(map[common.AddressBytes]struct{}) inputs := make(map[uint]uint64) @@ -1108,6 +1364,7 @@ func ValidateQiTxOutputsAndSignature(tx *types.Transaction, chain ChainContext, totalQitOut := big.NewInt(0) totalConvertQitOut := big.NewInt(0) conversion := false + wrapping := false pubKeys := make([]*btcec.PublicKey, 0, len(tx.TxIn())) addresses := make(map[common.AddressBytes]struct{}) for _, txIn := range tx.TxIn() { @@ -1143,7 +1400,7 @@ func ValidateQiTxOutputsAndSignature(tx *types.Transaction, chain ChainContext, } addresses[toAddr.Bytes20()] = struct{}{} - if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() { // Qi->Quai conversion + if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() && len(tx.Data()) == 0 { // Qi->Quai conversion conversion = true if currentHeader.NumberU64(common.ZONE_CTX) < params.GoldenAgeForkNumberV2 && txOut.Denomination < params.MinQiConversionDenomination { return nil, fmt.Errorf("tx %v emits UTXO with value %d less than minimum denomination %d", tx.Hash().Hex(), txOut.Denomination, params.MinQiConversionDenomination) @@ -1151,6 +1408,14 @@ func ValidateQiTxOutputsAndSignature(tx *types.Transaction, chain ChainContext, totalConvertQitOut.Add(totalConvertQitOut, types.Denominations[txOut.Denomination]) // Add to total conversion output for aggregation delete(addresses, toAddr.Bytes20()) continue + } else if toAddr.Location().Equal(location) && toAddr.IsInQiLedgerScope() && len(tx.Data()) != 0 { // Quai->Qi wrapping + ownerContract := common.BytesToAddress(tx.Data(), location) + if _, err := ownerContract.InternalAndQuaiAddress(); err != nil { + return nil, err + } + wrapping = true + totalConvertQitOut.Add(totalConvertQitOut, types.Denominations[txOut.Denomination]) // Uses the same path as conversion but takes priority + delete(addresses, toAddr.Bytes20()) } else if toAddr.IsInQuaiLedgerScope() { return nil, fmt.Errorf("tx [%v] emits UTXO with To address not in the Qi ledger scope", tx.Hash().Hex()) } @@ -1209,11 +1474,13 @@ func ValidateQiTxOutputsAndSignature(tx *types.Transaction, chain ChainContext, if txFeeInQuai.Cmp(minimumFeeInQuai) < 0 { return nil, fmt.Errorf("tx %032x has insufficient fee for base fee, have %d want %d", tx.Hash(), txFeeInQuai.Uint64(), minimumFeeInQuai.Uint64()) } - if conversion { - if currentHeader.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 && totalConvertQitOut.Cmp(types.Denominations[params.MinQiConversionDenomination]) < 0 { - return nil, fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), totalConvertQitOut.Uint64()) + if conversion && currentHeader.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 && totalConvertQitOut.Cmp(types.Denominations[params.MinQiConversionDenomination]) < 0 { + return nil, fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), totalConvertQitOut.Uint64()) + } + if conversion || wrapping { + if conversion && wrapping { + return nil, fmt.Errorf("tx %032x emits both a conversion and a wrapping UTXO", tx.Hash()) } - if currentHeader.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 { // Since this transaction contains a conversion, check if the required conversion gas is paid // The user must pay this to the miner now, but it is only added to the block gas limit when the ETX is played in the destination @@ -1277,6 +1544,9 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir if currentHeader == nil || batch == nil || gp == nil || usedGas == nil || signer == nil || etxRLimit == nil || etxPLimit == nil { return nil, nil, nil, errors.New("one of the parameters is nil"), nil } + if len(tx.Data()) != 0 && len(tx.Data()) != common.AddressLength { + return nil, nil, nil, fmt.Errorf("tx %v emits UTXO with invalid data length %d", tx.Hash().Hex(), len(tx.Data())), nil + } intrinsicGas := types.CalculateIntrinsicQiTxGas(tx, qiScalingFactor) *usedGas += intrinsicGas if err := gp.SubGas(intrinsicGas); err != nil { @@ -1351,6 +1621,7 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir totalQitOut := big.NewInt(0) totalConvertQitOut := big.NewInt(0) conversion := false + wrapping := false var convertAddress common.Address for txOutIdx, txOut := range tx.TxOut() { // It would be impossible for a tx to have this many outputs based on block gas limit, but cap it here anyways @@ -1376,7 +1647,7 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir addresses[toAddr.Bytes20()] = struct{}{} outputs[uint(txOut.Denomination)]++ - if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() { // Qi->Quai conversion + if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() && len(tx.Data()) == 0 { // Qi->Quai conversion conversion = true convertAddress = toAddr if currentHeader.NumberU64(common.ZONE_CTX) < params.GoldenAgeForkNumberV2 && txOut.Denomination < params.MinQiConversionDenomination { @@ -1386,6 +1657,16 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir outputs[uint(txOut.Denomination)] -= 1 // This output no longer exists because it has been aggregated delete(addresses, toAddr.Bytes20()) continue + } else if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() && len(tx.Data()) != 0 { // Wrapped Qi transaction + ownerContract := common.BytesToAddress(tx.Data(), location) + if _, err := ownerContract.InternalAndQuaiAddress(); err != nil { + return nil, nil, nil, err, nil + } + wrapping = true + convertAddress = toAddr + totalConvertQitOut.Add(totalConvertQitOut, types.Denominations[txOut.Denomination]) // Uses the same path as conversion but takes priority + outputs[uint(txOut.Denomination)] -= 1 // This output no longer exists because it has been aggregated + delete(addresses, toAddr.Bytes20()) } else if toAddr.IsInQuaiLedgerScope() { return nil, nil, nil, fmt.Errorf("tx %v emits UTXO with To address not in the Qi ledger scope", tx.Hash().Hex()), nil } @@ -1460,9 +1741,18 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir if txFeeInQuai.Cmp(minimumFeeInQuai) < 0 { return nil, nil, nil, fmt.Errorf("tx %032x has insufficient fee for base fee, have %d want %d", tx.Hash(), txFeeInQuai.Uint64(), minimumFeeInQuai.Uint64()), nil } - if conversion { - if currentHeader.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 && totalConvertQitOut.Cmp(types.Denominations[params.MinQiConversionDenomination]) < 0 { - return nil, nil, nil, fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), totalConvertQitOut.Uint64()), nil + if conversion && currentHeader.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 && totalConvertQitOut.Cmp(types.Denominations[params.MinQiConversionDenomination]) < 0 { + return nil, nil, nil, fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), totalConvertQitOut.Uint64()), nil + } + if conversion || wrapping { + if conversion && wrapping { + return nil, nil, nil, fmt.Errorf("tx %032x emits both a conversion and a wrapping UTXO", tx.Hash()), nil + } + etxType := types.ConversionType + data := []byte{} + if wrapping { + etxType = types.WrappingQiType + data = tx.Data() } var etxInner types.ExternalTx if currentHeader.NumberU64(common.ZONE_CTX) < params.GoldenAgeForkNumberV2 { @@ -1478,7 +1768,7 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir if ETXPCount > *etxPLimit { return nil, nil, nil, fmt.Errorf("tx [%v] emits too many cross-prime ETXs for block. emitted: %d, limit: %d", tx.Hash().Hex(), ETXPCount, etxPLimit), nil } - etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: types.ConversionType, OriginatingTxHash: tx.Hash(), Gas: remainingGas.Uint64()} // Value is in Qits not Denomination + etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: uint64(etxType), OriginatingTxHash: tx.Hash(), Gas: remainingGas.Uint64(), Data: data} // Value is in Qits not Denomination } else { // Since this transaction contains a conversion, check if the required conversion gas is paid // The user must pay this to the miner now, but it is only added to the block gas limit when the ETX is played in the destination @@ -1491,7 +1781,7 @@ func ProcessQiTx(tx *types.Transaction, chain ChainContext, checkSig bool, isFir if ETXPCount > *etxPLimit { return nil, nil, nil, fmt.Errorf("tx [%v] emits too many cross-prime ETXs for block. emitted: %d, limit: %d", tx.Hash().Hex(), ETXPCount, etxPLimit), nil } - etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: types.ConversionType, OriginatingTxHash: tx.Hash(), Gas: 0} // Value is in Qits not Denomination + etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: uint64(etxType), OriginatingTxHash: tx.Hash(), Gas: 0, Data: data} // Value is in Qits not Denomination } *usedGas += params.ETXGas if err := gp.SubGas(params.ETXGas); err != nil { @@ -1643,7 +1933,7 @@ func (p *StateProcessor) Apply(batch ethdb.Batch, block *types.WorkObject) ([]*t // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(config *params.ChainConfig, parent *types.WorkObject, parentOrder int, bc ChainContext, author *common.Address, gp *types.GasPool, statedb *state.StateDB, header *types.WorkObject, tx *types.Transaction, usedGas *uint64, usedState *uint64, cfg vm.Config, etxRLimit, etxPLimit *int, logger *log.Logger) (*types.Receipt, *big.Int, error) { +func ApplyTransaction(config *params.ChainConfig, parent *types.WorkObject, parentOrder int, bc ChainContext, author *common.Address, gp *types.GasPool, statedb *state.StateDB, header *types.WorkObject, tx *types.Transaction, usedGas *uint64, usedState *uint64, cfg vm.Config, etxRLimit, etxPLimit *int, batch ethdb.Batch, logger *log.Logger) (*types.Receipt, *big.Int, error) { nodeCtx := config.Location.Context() msg, err := tx.AsMessage(types.MakeSigner(config, header.Number(nodeCtx)), header.BaseFee()) if err != nil { @@ -1654,7 +1944,7 @@ func ApplyTransaction(config *params.ChainConfig, parent *types.WorkObject, pare if err != nil { return nil, nil, err } - vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg, batch) if tx.Type() == types.ExternalTxType { prevZeroBal := prepareApplyETX(statedb, msg.Value(), config.Location) receipt, quaiFees, err := applyTransaction(msg, parent, config, bc, gp, statedb, header.Number(nodeCtx), header.Hash(), tx, usedGas, usedState, vmenv, etxRLimit, etxPLimit, logger) @@ -1935,7 +2225,7 @@ func (p *StateProcessor) StateAtTransaction(block *types.WorkObject, txIndex int return msg, context, statedb, nil } // Not yet the searched for transaction, execute on top of the current state - vmenv := vm.NewEVM(context, txContext, statedb, p.hc.Config(), vm.Config{}) + vmenv := vm.NewEVM(context, txContext, statedb, p.hc.Config(), vm.Config{}, nil) statedb.Prepare(tx.Hash(), idx) if _, err := ApplyMessage(vmenv, msg, new(types.GasPool).AddGas(tx.Gas())); err != nil { return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) @@ -1966,3 +2256,9 @@ func prepareApplyETX(statedb *state.StateDB, value *big.Int, nodeLocation common statedb.SetBalance(common.ZeroInternal(nodeLocation), value) // Use zero address at temp placeholder and set it to value return prevZeroBal } + +func prepareApplyCoinbaseLockup(statedb *state.StateDB, nodeLocation common.Location) *big.Int { + prevZeroBal := statedb.GetBalance(common.OneInternal(nodeLocation)) // Get current zero address balance + statedb.SetBalance(common.OneInternal(nodeLocation), params.BigEther) // Use zero address at temp placeholder and set it to one Quai for gas + return prevZeroBal +} diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 06ca04f8aa..5f541a8f9f 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -335,6 +335,7 @@ func (wh *WorkObjectHeader) MarshalJSON() ([]byte, error) { Nonce BlockNonce `json:"nonce" gencoden:"required"` Lock hexutil.Uint64 `json:"lock" gencoden:"required"` PrimaryCoinbase string `json:"primaryCoinbase" gencoden:"required"` + Data hexutil.Bytes `json:"data" gencoden:"required"` } enc.HeaderHash = wh.HeaderHash() @@ -348,6 +349,7 @@ func (wh *WorkObjectHeader) MarshalJSON() ([]byte, error) { enc.Nonce = wh.Nonce() enc.Lock = hexutil.Uint64(wh.Lock()) enc.PrimaryCoinbase = wh.PrimaryCoinbase().Hex() + enc.Data = wh.Data() raw, err := json.Marshal(&enc) return raw, err @@ -367,6 +369,7 @@ func (wh *WorkObjectHeader) UnmarshalJSON(input []byte) error { Nonce BlockNonce `json:"nonce" gencoden:"required"` Lock hexutil.Uint64 `json:"lock" gencoden:"required"` PrimaryCoinbase string `json:"primaryCoinbase" gencoden:"required"` + Data hexutil.Bytes `json:"data" gencoden:"required"` } err := json.Unmarshal(input, &dec) @@ -393,6 +396,7 @@ func (wh *WorkObjectHeader) UnmarshalJSON(input []byte) error { return err } wh.SetPrimaryCoinbase(coinbaseAddr.Address()) + wh.SetData(dec.Data) return nil } @@ -472,4 +476,4 @@ func (wo *WorkObject) UnmarshalJSON(input []byte) error { wo.SetBody(dec.WoBody) wo.SetTx(dec.Tx) return nil -} \ No newline at end of file +} diff --git a/core/types/proto_block.pb.go b/core/types/proto_block.pb.go index f1e6fcc2c9..0a8cfc0566 100644 --- a/core/types/proto_block.pb.go +++ b/core/types/proto_block.pb.go @@ -764,6 +764,7 @@ type ProtoWorkObjectHeader struct { PrimeTerminusNumber []byte `protobuf:"bytes,10,opt,name=prime_terminus_number,json=primeTerminusNumber,proto3,oneof" json:"prime_terminus_number,omitempty"` Lock *uint32 `protobuf:"varint,11,opt,name=lock,proto3,oneof" json:"lock,omitempty"` PrimaryCoinbase *common.ProtoAddress `protobuf:"bytes,12,opt,name=primary_coinbase,json=primaryCoinbase,proto3,oneof" json:"primary_coinbase,omitempty"` + Data []byte `protobuf:"bytes,13,opt,name=data,proto3" json:"data,omitempty"` } func (x *ProtoWorkObjectHeader) Reset() { @@ -880,6 +881,13 @@ func (x *ProtoWorkObjectHeader) GetPrimaryCoinbase() *common.ProtoAddress { return nil } +func (x *ProtoWorkObjectHeader) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + type ProtoWorkObjectHeaders struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2364,6 +2372,104 @@ func (x *ProtoKeys) GetKeys() [][]byte { return nil } +type ProtoKeyValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *ProtoKeyValue) Reset() { + *x = ProtoKeyValue{} + mi := &file_core_types_proto_block_proto_msgTypes[35] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProtoKeyValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProtoKeyValue) ProtoMessage() {} + +func (x *ProtoKeyValue) ProtoReflect() protoreflect.Message { + mi := &file_core_types_proto_block_proto_msgTypes[35] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProtoKeyValue.ProtoReflect.Descriptor instead. +func (*ProtoKeyValue) Descriptor() ([]byte, []int) { + return file_core_types_proto_block_proto_rawDescGZIP(), []int{35} +} + +func (x *ProtoKeyValue) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +func (x *ProtoKeyValue) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +type ProtoKeysAndValues struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + KeysAndValues []*ProtoKeyValue `protobuf:"bytes,1,rep,name=keys_and_values,json=keysAndValues,proto3" json:"keys_and_values,omitempty"` +} + +func (x *ProtoKeysAndValues) Reset() { + *x = ProtoKeysAndValues{} + mi := &file_core_types_proto_block_proto_msgTypes[36] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProtoKeysAndValues) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProtoKeysAndValues) ProtoMessage() {} + +func (x *ProtoKeysAndValues) ProtoReflect() protoreflect.Message { + mi := &file_core_types_proto_block_proto_msgTypes[36] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProtoKeysAndValues.ProtoReflect.Descriptor instead. +func (*ProtoKeysAndValues) Descriptor() ([]byte, []int) { + return file_core_types_proto_block_proto_rawDescGZIP(), []int{36} +} + +func (x *ProtoKeysAndValues) GetKeysAndValues() []*ProtoKeyValue { + if x != nil { + return x.KeysAndValues + } + return nil +} + type ProtoTrimDepths struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2374,7 +2480,7 @@ type ProtoTrimDepths struct { func (x *ProtoTrimDepths) Reset() { *x = ProtoTrimDepths{} - mi := &file_core_types_proto_block_proto_msgTypes[35] + mi := &file_core_types_proto_block_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2386,7 +2492,7 @@ func (x *ProtoTrimDepths) String() string { func (*ProtoTrimDepths) ProtoMessage() {} func (x *ProtoTrimDepths) ProtoReflect() protoreflect.Message { - mi := &file_core_types_proto_block_proto_msgTypes[35] + mi := &file_core_types_proto_block_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2399,7 +2505,7 @@ func (x *ProtoTrimDepths) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoTrimDepths.ProtoReflect.Descriptor instead. func (*ProtoTrimDepths) Descriptor() ([]byte, []int) { - return file_core_types_proto_block_proto_rawDescGZIP(), []int{35} + return file_core_types_proto_block_proto_rawDescGZIP(), []int{37} } func (x *ProtoTrimDepths) GetTrimDepths() map[uint32]uint64 { @@ -2419,7 +2525,7 @@ type ProtoTokenChoiceSet struct { func (x *ProtoTokenChoiceSet) Reset() { *x = ProtoTokenChoiceSet{} - mi := &file_core_types_proto_block_proto_msgTypes[36] + mi := &file_core_types_proto_block_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2431,7 +2537,7 @@ func (x *ProtoTokenChoiceSet) String() string { func (*ProtoTokenChoiceSet) ProtoMessage() {} func (x *ProtoTokenChoiceSet) ProtoReflect() protoreflect.Message { - mi := &file_core_types_proto_block_proto_msgTypes[36] + mi := &file_core_types_proto_block_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2444,7 +2550,7 @@ func (x *ProtoTokenChoiceSet) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoTokenChoiceSet.ProtoReflect.Descriptor instead. func (*ProtoTokenChoiceSet) Descriptor() ([]byte, []int) { - return file_core_types_proto_block_proto_rawDescGZIP(), []int{36} + return file_core_types_proto_block_proto_rawDescGZIP(), []int{38} } func (x *ProtoTokenChoiceSet) GetTokenChoiceArray() []*ProtoTokenChoiceArray { @@ -2464,7 +2570,7 @@ type ProtoTokenChoiceArray struct { func (x *ProtoTokenChoiceArray) Reset() { *x = ProtoTokenChoiceArray{} - mi := &file_core_types_proto_block_proto_msgTypes[37] + mi := &file_core_types_proto_block_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2476,7 +2582,7 @@ func (x *ProtoTokenChoiceArray) String() string { func (*ProtoTokenChoiceArray) ProtoMessage() {} func (x *ProtoTokenChoiceArray) ProtoReflect() protoreflect.Message { - mi := &file_core_types_proto_block_proto_msgTypes[37] + mi := &file_core_types_proto_block_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2489,7 +2595,7 @@ func (x *ProtoTokenChoiceArray) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoTokenChoiceArray.ProtoReflect.Descriptor instead. func (*ProtoTokenChoiceArray) Descriptor() ([]byte, []int) { - return file_core_types_proto_block_proto_rawDescGZIP(), []int{37} + return file_core_types_proto_block_proto_rawDescGZIP(), []int{39} } func (x *ProtoTokenChoiceArray) GetTokenChoices() *ProtoTokenChoice { @@ -2511,7 +2617,7 @@ type ProtoTokenChoice struct { func (x *ProtoTokenChoice) Reset() { *x = ProtoTokenChoice{} - mi := &file_core_types_proto_block_proto_msgTypes[38] + mi := &file_core_types_proto_block_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2523,7 +2629,7 @@ func (x *ProtoTokenChoice) String() string { func (*ProtoTokenChoice) ProtoMessage() {} func (x *ProtoTokenChoice) ProtoReflect() protoreflect.Message { - mi := &file_core_types_proto_block_proto_msgTypes[38] + mi := &file_core_types_proto_block_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2536,7 +2642,7 @@ func (x *ProtoTokenChoice) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoTokenChoice.ProtoReflect.Descriptor instead. func (*ProtoTokenChoice) Descriptor() ([]byte, []int) { - return file_core_types_proto_block_proto_rawDescGZIP(), []int{38} + return file_core_types_proto_block_proto_rawDescGZIP(), []int{40} } func (x *ProtoTokenChoice) GetQuai() uint64 { @@ -2571,7 +2677,7 @@ type ProtoBetas struct { func (x *ProtoBetas) Reset() { *x = ProtoBetas{} - mi := &file_core_types_proto_block_proto_msgTypes[39] + mi := &file_core_types_proto_block_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2583,7 +2689,7 @@ func (x *ProtoBetas) String() string { func (*ProtoBetas) ProtoMessage() {} func (x *ProtoBetas) ProtoReflect() protoreflect.Message { - mi := &file_core_types_proto_block_proto_msgTypes[39] + mi := &file_core_types_proto_block_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2596,7 +2702,7 @@ func (x *ProtoBetas) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoBetas.ProtoReflect.Descriptor instead. func (*ProtoBetas) Descriptor() ([]byte, []int) { - return file_core_types_proto_block_proto_rawDescGZIP(), []int{39} + return file_core_types_proto_block_proto_rawDescGZIP(), []int{41} } func (x *ProtoBetas) GetBeta0() []byte { @@ -2624,7 +2730,7 @@ type ProtoLockup struct { func (x *ProtoLockup) Reset() { *x = ProtoLockup{} - mi := &file_core_types_proto_block_proto_msgTypes[40] + mi := &file_core_types_proto_block_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2636,7 +2742,7 @@ func (x *ProtoLockup) String() string { func (*ProtoLockup) ProtoMessage() {} func (x *ProtoLockup) ProtoReflect() protoreflect.Message { - mi := &file_core_types_proto_block_proto_msgTypes[40] + mi := &file_core_types_proto_block_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2649,7 +2755,7 @@ func (x *ProtoLockup) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoLockup.ProtoReflect.Descriptor instead. func (*ProtoLockup) Descriptor() ([]byte, []int) { - return file_core_types_proto_block_proto_rawDescGZIP(), []int{40} + return file_core_types_proto_block_proto_rawDescGZIP(), []int{42} } func (x *ProtoLockup) GetValue() []byte { @@ -2676,7 +2782,7 @@ type ProtoLockups struct { func (x *ProtoLockups) Reset() { *x = ProtoLockups{} - mi := &file_core_types_proto_block_proto_msgTypes[41] + mi := &file_core_types_proto_block_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2688,7 +2794,7 @@ func (x *ProtoLockups) String() string { func (*ProtoLockups) ProtoMessage() {} func (x *ProtoLockups) ProtoReflect() protoreflect.Message { - mi := &file_core_types_proto_block_proto_msgTypes[41] + mi := &file_core_types_proto_block_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2701,7 +2807,7 @@ func (x *ProtoLockups) ProtoReflect() protoreflect.Message { // Deprecated: Use ProtoLockups.ProtoReflect.Descriptor instead. func (*ProtoLockups) Descriptor() ([]byte, []int) { - return file_core_types_proto_block_proto_rawDescGZIP(), []int{41} + return file_core_types_proto_block_proto_rawDescGZIP(), []int{43} } func (x *ProtoLockups) GetLockups() []*ProtoLockup { @@ -2945,7 +3051,7 @@ var file_core_types_proto_block_proto_rawDesc = []byte{ 0x12, 0x3c, 0x0a, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x75, 0x70, 0x6c, 0x65, - 0x52, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x22, 0xde, + 0x52, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x73, 0x22, 0xf2, 0x05, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x37, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, @@ -2981,297 +3087,308 @@ var file_core_types_proto_block_proto_rawDesc = []byte{ 0x73, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x48, 0x0b, 0x52, 0x0f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, - 0x65, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, - 0x68, 0x61, 0x73, 0x68, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, - 0x68, 0x61, 0x73, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x42, - 0x0d, 0x0a, 0x0b, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x42, 0x0a, - 0x0a, 0x08, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x6e, - 0x6f, 0x6e, 0x63, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x69, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x42, 0x07, - 0x0a, 0x05, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x70, 0x72, 0x69, 0x6d, - 0x65, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, - 0x72, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x70, - 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x22, - 0x55, 0x0a, 0x16, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x3b, 0x0a, 0x0a, 0x77, 0x6f, 0x5f, - 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x09, 0x77, 0x6f, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x22, 0xe0, 0x03, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x2f, - 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, - 0x41, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, - 0x01, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x88, - 0x01, 0x01, 0x12, 0x3a, 0x0a, 0x06, 0x75, 0x6e, 0x63, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x73, 0x48, 0x02, 0x52, 0x06, 0x75, 0x6e, 0x63, 0x6c, 0x65, 0x73, 0x88, 0x01, 0x01, 0x12, 0x42, - 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x74, 0x78, 0x73, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, - 0x03, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x45, 0x74, 0x78, 0x73, 0x88, - 0x01, 0x01, 0x12, 0x35, 0x0a, 0x08, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x48, 0x04, 0x52, 0x08, 0x6d, 0x61, - 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x43, 0x0a, 0x10, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x06, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x48, 0x05, 0x52, 0x0f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6c, 0x69, 0x6e, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x88, 0x01, 0x01, 0x42, 0x09, - 0x0a, 0x07, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x75, - 0x6e, 0x63, 0x6c, 0x65, 0x73, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, - 0x6e, 0x64, 0x5f, 0x65, 0x74, 0x78, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x61, 0x6e, 0x69, - 0x66, 0x65, 0x73, 0x74, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x69, - 0x6e, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x22, 0xda, 0x01, 0x0a, 0x0f, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x3e, 0x0a, - 0x09, 0x77, 0x6f, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, - 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00, - 0x52, 0x08, 0x77, 0x6f, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x38, 0x0a, - 0x07, 0x77, 0x6f, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, - 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x48, 0x01, 0x52, 0x06, 0x77, 0x6f, - 0x42, 0x6f, 0x64, 0x79, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x02, 0x74, 0x78, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x02, 0x52, 0x02, - 0x74, 0x78, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x77, 0x6f, 0x5f, 0x68, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x77, 0x6f, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x42, - 0x05, 0x0a, 0x03, 0x5f, 0x74, 0x78, 0x22, 0x4d, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, - 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x0c, 0x77, 0x6f, - 0x72, 0x6b, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, - 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x4f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x73, 0x22, 0x68, 0x0a, 0x18, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, - 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x56, 0x69, 0x65, + 0x65, 0x88, 0x01, 0x01, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0d, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x68, 0x65, 0x61, + 0x64, 0x65, 0x72, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x70, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x6e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, + 0x74, 0x79, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x42, 0x08, + 0x0a, 0x06, 0x5f, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6c, 0x6f, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6d, 0x69, 0x78, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x42, 0x18, 0x0a, 0x16, 0x5f, + 0x70, 0x72, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x75, 0x73, 0x5f, 0x6e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x42, 0x13, + 0x0a, 0x11, 0x5f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x69, 0x6e, 0x62, + 0x61, 0x73, 0x65, 0x22, 0x55, 0x0a, 0x16, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x3b, 0x0a, + 0x0a, 0x77, 0x6f, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, + 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, + 0x09, 0x77, 0x6f, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x22, 0xe0, 0x03, 0x0a, 0x13, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x6f, + 0x64, 0x79, 0x12, 0x2f, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x88, 0x01, 0x01, 0x12, 0x41, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x48, 0x01, 0x52, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x88, 0x01, 0x01, 0x12, 0x3a, 0x0a, 0x06, 0x75, 0x6e, 0x63, 0x6c, 0x65, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x73, 0x48, 0x02, 0x52, 0x06, 0x75, 0x6e, 0x63, 0x6c, 0x65, 0x73, 0x88, + 0x01, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x65, + 0x74, 0x78, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x48, 0x03, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x45, + 0x74, 0x78, 0x73, 0x88, 0x01, 0x01, 0x12, 0x35, 0x0a, 0x08, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, + 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x48, 0x04, + 0x52, 0x08, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x88, 0x01, 0x01, 0x12, 0x43, 0x0a, + 0x10, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, + 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x48, 0x05, 0x52, 0x0f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x88, + 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x0f, 0x0a, + 0x0d, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x09, + 0x0a, 0x07, 0x5f, 0x75, 0x6e, 0x63, 0x6c, 0x65, 0x73, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x6f, 0x75, + 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x74, 0x78, 0x73, 0x42, 0x0b, 0x0a, 0x09, 0x5f, + 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x42, 0x13, 0x0a, 0x11, 0x5f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x22, 0xda, 0x01, + 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x12, 0x3e, 0x0a, 0x09, 0x77, 0x6f, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x48, 0x00, 0x52, 0x08, 0x77, 0x6f, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x88, 0x01, + 0x01, 0x12, 0x38, 0x0a, 0x07, 0x77, 0x6f, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x48, 0x01, + 0x52, 0x06, 0x77, 0x6f, 0x42, 0x6f, 0x64, 0x79, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x02, 0x74, + 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x48, 0x02, 0x52, 0x02, 0x74, 0x78, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x77, 0x6f, + 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x77, 0x6f, 0x5f, 0x62, + 0x6f, 0x64, 0x79, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x74, 0x78, 0x22, 0x4d, 0x0a, 0x10, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x12, 0x39, + 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x52, 0x0b, 0x77, 0x6f, + 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x22, 0x68, 0x0a, 0x18, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x56, 0x69, 0x65, 0x77, 0x12, 0x3c, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6f, 0x62, + 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, + 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x22, 0x5f, 0x0a, 0x19, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x56, 0x69, 0x65, 0x77, + 0x12, 0x42, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x56, 0x69, 0x65, 0x77, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x73, 0x22, 0x69, 0x0a, 0x19, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, + 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x69, 0x65, 0x77, 0x12, 0x3c, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, - 0x5f, 0x0a, 0x19, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x56, 0x69, 0x65, 0x77, 0x12, 0x42, 0x0a, 0x0c, - 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x56, - 0x69, 0x65, 0x77, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, - 0x22, 0x69, 0x0a, 0x19, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x56, 0x69, 0x65, 0x77, 0x12, 0x3c, 0x0a, - 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x77, 0x6f, - 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, - 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x68, 0x0a, 0x18, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x53, 0x68, - 0x61, 0x72, 0x65, 0x56, 0x69, 0x65, 0x77, 0x12, 0x3c, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x5f, - 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, - 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, - 0x63, 0x74, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x60, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x41, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x0b, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x52, 0x0a, 0x73, 0x74, 0x6f, - 0x72, 0x61, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xf0, 0x02, 0x0a, 0x16, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x46, 0x6f, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x61, - 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x14, 0x70, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x5f, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x11, 0x70, 0x6f, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4f, 0x72, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, - 0x65, 0x5f, 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x11, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x47, 0x61, 0x73, 0x55, - 0x73, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, - 0x6f, 0x67, 0x73, 0x46, 0x6f, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x04, 0x6c, - 0x6f, 0x67, 0x73, 0x12, 0x2a, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x52, 0x06, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, - 0x3f, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, - 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, - 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x19, 0x0a, 0x08, 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x07, 0x67, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x3d, 0x0a, 0x0d, 0x6f, - 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x74, 0x78, 0x73, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0c, 0x6f, 0x75, - 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x45, 0x74, 0x78, 0x73, 0x22, 0x54, 0x0a, 0x17, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x73, 0x46, 0x6f, 0x72, 0x53, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x46, 0x6f, 0x72, 0x53, - 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x73, - 0x22, 0x83, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, 0x6f, 0x67, 0x46, 0x6f, 0x72, - 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x29, 0x0a, 0x06, 0x74, 0x6f, 0x70, 0x69, 0x63, - 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, - 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x52, 0x06, 0x74, 0x6f, 0x70, 0x69, - 0x63, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x44, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, - 0x6f, 0x67, 0x73, 0x46, 0x6f, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x2d, 0x0a, - 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, 0x6f, 0x67, 0x46, 0x6f, 0x72, 0x53, - 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x22, 0x88, 0x01, 0x0a, - 0x12, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x61, - 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x02, 0x77, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, - 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x00, 0x52, 0x02, 0x77, 0x6f, 0x88, 0x01, 0x01, - 0x12, 0x32, 0x0a, 0x07, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, - 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x48, 0x01, 0x52, 0x07, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, - 0x69, 0x88, 0x01, 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x77, 0x6f, 0x42, 0x0a, 0x0a, 0x08, 0x5f, - 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x22, 0x76, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x12, 0x32, 0x0a, 0x0b, 0x64, 0x6f, 0x6d, 0x5f, 0x74, - 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, + 0x68, 0x0a, 0x18, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x53, 0x68, 0x61, 0x72, 0x65, 0x56, 0x69, 0x65, 0x77, 0x12, 0x3c, 0x0a, 0x0b, 0x77, + 0x6f, 0x72, 0x6b, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, + 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, + 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x88, 0x01, 0x01, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x77, 0x6f, + 0x72, 0x6b, 0x5f, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x60, 0x0a, 0x10, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x0b, 0x73, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x52, - 0x0a, 0x64, 0x6f, 0x6d, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x12, 0x32, 0x0a, 0x0b, 0x73, - 0x75, 0x62, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, - 0x61, 0x73, 0x68, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x22, - 0x40, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x45, 0x74, 0x78, 0x53, 0x65, 0x74, 0x12, 0x22, - 0x0a, 0x0a, 0x65, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x48, 0x00, 0x52, 0x09, 0x65, 0x74, 0x78, 0x48, 0x61, 0x73, 0x68, 0x65, 0x73, 0x88, - 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x65, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, - 0x73, 0x22, 0xa8, 0x01, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x45, 0x74, 0x78, 0x73, 0x12, 0x33, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x00, - 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x42, 0x0a, 0x0d, 0x6f, - 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x74, 0x78, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x01, 0x52, 0x0c, - 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x45, 0x74, 0x78, 0x73, 0x88, 0x01, 0x01, 0x42, - 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x6f, - 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x74, 0x78, 0x73, 0x22, 0xa8, 0x01, 0x0a, - 0x16, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x74, 0x78, - 0x73, 0x52, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x12, 0x33, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, - 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, - 0x00, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, 0x3e, 0x0a, 0x0b, - 0x65, 0x74, 0x78, 0x73, 0x5f, 0x72, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x01, 0x52, 0x0a, 0x65, - 0x74, 0x78, 0x73, 0x52, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, - 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, 0x65, 0x74, 0x78, 0x73, - 0x5f, 0x72, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x22, 0x35, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x54, 0x78, 0x49, 0x6e, 0x73, 0x12, 0x27, 0x0a, 0x06, 0x74, 0x78, 0x5f, 0x69, 0x6e, 0x73, 0x18, - 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x49, 0x6e, 0x52, 0x05, 0x74, 0x78, 0x49, 0x6e, 0x73, 0x22, 0x39, - 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x73, 0x12, 0x2a, 0x0a, - 0x07, 0x74, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, - 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x4f, 0x75, - 0x74, 0x52, 0x06, 0x74, 0x78, 0x4f, 0x75, 0x74, 0x73, 0x22, 0x95, 0x01, 0x0a, 0x09, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x49, 0x6e, 0x12, 0x47, 0x0a, 0x12, 0x70, 0x72, 0x65, 0x76, 0x69, - 0x6f, 0x75, 0x73, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x70, 0x72, 0x65, - 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x88, 0x01, 0x01, - 0x12, 0x1c, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x48, 0x01, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x42, 0x15, - 0x0a, 0x13, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x6f, 0x75, 0x74, 0x5f, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, - 0x79, 0x22, 0x69, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, - 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x0a, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x22, 0xf0, 0x02, 0x0a, 0x16, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x46, 0x6f, 0x72, 0x53, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x2f, 0x0a, 0x14, 0x70, 0x6f, 0x73, 0x74, 0x5f, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x5f, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x70, 0x6f, 0x73, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4f, + 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x75, 0x6d, 0x75, 0x6c, + 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x47, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x4c, 0x6f, 0x67, 0x73, 0x46, 0x6f, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x2a, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, + 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x52, 0x06, 0x74, 0x78, 0x48, + 0x61, 0x73, 0x68, 0x12, 0x3f, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x5f, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x74, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, + 0x3d, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x74, 0x78, 0x73, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x45, 0x74, 0x78, 0x73, 0x22, 0x54, + 0x0a, 0x17, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x73, 0x46, + 0x6f, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x39, 0x0a, 0x08, 0x72, 0x65, 0x63, + 0x65, 0x69, 0x70, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, + 0x46, 0x6f, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x08, 0x72, 0x65, 0x63, 0x65, + 0x69, 0x70, 0x74, 0x73, 0x22, 0x83, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, 0x6f, + 0x67, 0x46, 0x6f, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x2e, 0x0a, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, + 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x29, 0x0a, 0x06, 0x74, + 0x6f, 0x70, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x52, 0x06, + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x44, 0x0a, 0x13, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x4c, 0x6f, 0x67, 0x73, 0x46, 0x6f, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, + 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, 0x6f, 0x67, + 0x46, 0x6f, 0x72, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, + 0x22, 0x88, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x2b, 0x0a, 0x02, 0x77, 0x6f, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x00, 0x52, 0x02, 0x77, + 0x6f, 0x88, 0x01, 0x01, 0x12, 0x32, 0x0a, 0x07, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x48, 0x01, 0x52, 0x07, 0x74, 0x65, + 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x88, 0x01, 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x77, 0x6f, 0x42, + 0x0a, 0x0a, 0x08, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x22, 0x76, 0x0a, 0x0c, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x12, 0x32, 0x0a, 0x0b, 0x64, + 0x6f, 0x6d, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x48, - 0x61, 0x73, 0x68, 0x48, 0x00, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x88, 0x01, 0x01, 0x12, 0x19, - 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, 0x52, - 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x68, 0x61, - 0x73, 0x68, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x93, 0x01, 0x0a, - 0x0a, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x12, 0x27, 0x0a, 0x0c, 0x64, - 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0d, 0x48, 0x00, 0x52, 0x0c, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0c, 0x48, 0x02, 0x52, 0x04, 0x6c, 0x6f, 0x63, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x0f, 0x0a, 0x0d, - 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0a, 0x0a, - 0x08, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6c, 0x6f, - 0x63, 0x6b, 0x22, 0xd4, 0x01, 0x0a, 0x1c, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4f, 0x75, 0x74, 0x50, - 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6e, 0x64, 0x44, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x48, 0x61, 0x73, 0x68, 0x48, 0x00, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x88, 0x01, 0x01, 0x12, - 0x19, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x01, - 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x88, 0x01, 0x01, 0x12, 0x27, 0x0a, 0x0c, 0x64, 0x65, - 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, - 0x48, 0x02, 0x52, 0x0c, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0c, 0x48, 0x03, 0x52, 0x04, 0x6c, 0x6f, 0x63, 0x6b, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, 0x05, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x42, - 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x5b, 0x0a, 0x15, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, - 0x74, 0x73, 0x12, 0x42, 0x0a, 0x0a, 0x6f, 0x75, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6e, 0x64, 0x44, - 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x75, 0x74, - 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x53, 0x70, 0x65, 0x6e, 0x74, 0x55, 0x54, 0x58, 0x4f, 0x12, 0x35, 0x0a, 0x08, 0x6f, 0x75, 0x74, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x6c, - 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, - 0x74, 0x48, 0x00, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x88, 0x01, 0x01, - 0x12, 0x2c, 0x0a, 0x05, 0x73, 0x75, 0x74, 0x78, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x11, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x4f, - 0x75, 0x74, 0x48, 0x01, 0x52, 0x05, 0x73, 0x75, 0x74, 0x78, 0x6f, 0x88, 0x01, 0x01, 0x42, 0x0b, - 0x0a, 0x09, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x42, 0x08, 0x0a, 0x06, 0x5f, - 0x73, 0x75, 0x74, 0x78, 0x6f, 0x22, 0x40, 0x0a, 0x0f, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x53, 0x70, - 0x65, 0x6e, 0x74, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x12, 0x2d, 0x0a, 0x06, 0x73, 0x75, 0x74, 0x78, - 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, - 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x55, 0x54, 0x58, 0x4f, 0x52, - 0x06, 0x73, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x1f, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x4b, 0x65, 0x79, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0c, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x0f, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x54, 0x72, 0x69, 0x6d, 0x44, 0x65, 0x70, 0x74, 0x68, 0x73, 0x12, 0x47, 0x0a, 0x0b, - 0x74, 0x72, 0x69, 0x6d, 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x26, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, - 0x72, 0x69, 0x6d, 0x44, 0x65, 0x70, 0x74, 0x68, 0x73, 0x2e, 0x54, 0x72, 0x69, 0x6d, 0x44, 0x65, - 0x70, 0x74, 0x68, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x74, 0x72, 0x69, 0x6d, 0x44, - 0x65, 0x70, 0x74, 0x68, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x54, 0x72, 0x69, 0x6d, 0x44, 0x65, 0x70, - 0x74, 0x68, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0x61, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x4a, 0x0a, 0x12, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x72, 0x72, 0x61, - 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, - 0x41, 0x72, 0x72, 0x61, 0x79, 0x52, 0x10, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, - 0x63, 0x65, 0x41, 0x72, 0x72, 0x61, 0x79, 0x22, 0x6c, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x41, 0x72, 0x72, 0x61, 0x79, - 0x12, 0x41, 0x0a, 0x0d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x6f, 0x69, 0x63, 0x65, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, - 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, - 0x48, 0x00, 0x52, 0x0c, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x73, - 0x88, 0x01, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x68, - 0x6f, 0x69, 0x63, 0x65, 0x73, 0x22, 0x4a, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x71, 0x75, 0x61, - 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x71, 0x75, 0x61, 0x69, 0x12, 0x0e, 0x0a, - 0x02, 0x71, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x71, 0x69, 0x12, 0x12, 0x0a, - 0x04, 0x64, 0x69, 0x66, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x69, 0x66, - 0x66, 0x22, 0x38, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x65, 0x74, 0x61, 0x73, 0x12, - 0x14, 0x0a, 0x05, 0x62, 0x65, 0x74, 0x61, 0x30, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x62, 0x65, 0x74, 0x61, 0x30, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x65, 0x74, 0x61, 0x31, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x65, 0x74, 0x61, 0x31, 0x22, 0x48, 0x0a, 0x0b, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x4c, 0x6f, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x48, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3c, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, 0x6f, - 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x07, 0x6c, 0x6f, 0x63, 0x6b, 0x75, 0x70, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x4c, 0x6f, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x07, 0x6c, 0x6f, 0x63, 0x6b, - 0x75, 0x70, 0x73, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x64, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x69, 0x65, 0x73, 0x2f, 0x67, 0x6f, 0x2d, 0x71, 0x75, 0x61, 0x69, 0x2f, 0x63, 0x6f, - 0x72, 0x65, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x73, 0x68, 0x52, 0x0a, 0x64, 0x6f, 0x6d, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x12, + 0x32, 0x0a, 0x0b, 0x73, 0x75, 0x62, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x52, 0x0a, 0x73, 0x75, 0x62, 0x54, 0x65, 0x72, 0x6d, + 0x69, 0x6e, 0x69, 0x22, 0x40, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x45, 0x74, 0x78, 0x53, + 0x65, 0x74, 0x12, 0x22, 0x0a, 0x0a, 0x65, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x09, 0x65, 0x74, 0x78, 0x48, 0x61, 0x73, + 0x68, 0x65, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x65, 0x74, 0x78, 0x5f, 0x68, + 0x61, 0x73, 0x68, 0x65, 0x73, 0x22, 0xa8, 0x01, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x45, 0x74, 0x78, 0x73, 0x12, 0x33, 0x0a, 0x06, 0x68, 0x65, + 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, 0x65, + 0x63, 0x74, 0x48, 0x00, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, 0x12, + 0x42, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x74, 0x78, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x48, 0x01, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x45, 0x74, 0x78, 0x73, + 0x88, 0x01, 0x01, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x10, + 0x0a, 0x0e, 0x5f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x65, 0x74, 0x78, 0x73, + 0x22, 0xa8, 0x01, 0x0a, 0x16, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x45, 0x74, 0x78, 0x73, 0x52, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x12, 0x33, 0x0a, 0x06, 0x68, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x57, 0x6f, 0x72, 0x6b, 0x4f, 0x62, 0x6a, + 0x65, 0x63, 0x74, 0x48, 0x00, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x88, 0x01, 0x01, + 0x12, 0x3e, 0x0a, 0x0b, 0x65, 0x74, 0x78, 0x73, 0x5f, 0x72, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x48, + 0x01, 0x52, 0x0a, 0x65, 0x74, 0x78, 0x73, 0x52, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x88, 0x01, 0x01, + 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x42, 0x0e, 0x0a, 0x0c, 0x5f, + 0x65, 0x74, 0x78, 0x73, 0x5f, 0x72, 0x6f, 0x6c, 0x6c, 0x75, 0x70, 0x22, 0x35, 0x0a, 0x0a, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x49, 0x6e, 0x73, 0x12, 0x27, 0x0a, 0x06, 0x74, 0x78, 0x5f, + 0x69, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x62, 0x6c, 0x6f, 0x63, + 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x49, 0x6e, 0x52, 0x05, 0x74, 0x78, 0x49, + 0x6e, 0x73, 0x22, 0x39, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x4f, 0x75, 0x74, + 0x73, 0x12, 0x2a, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x6f, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x54, 0x78, 0x4f, 0x75, 0x74, 0x52, 0x06, 0x74, 0x78, 0x4f, 0x75, 0x74, 0x73, 0x22, 0x95, 0x01, + 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x49, 0x6e, 0x12, 0x47, 0x0a, 0x12, 0x70, + 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x6f, 0x75, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, + 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, + 0x74, 0x88, 0x01, 0x01, 0x12, 0x1c, 0x0a, 0x07, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x06, 0x70, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x88, + 0x01, 0x01, 0x42, 0x15, 0x0a, 0x13, 0x5f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, + 0x6f, 0x75, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x75, + 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x22, 0x69, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4f, 0x75, + 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x48, 0x00, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x88, + 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0d, 0x48, 0x01, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x88, 0x01, 0x01, 0x42, 0x07, 0x0a, + 0x05, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x22, 0x93, 0x01, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x12, + 0x27, 0x0a, 0x0c, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0d, 0x48, 0x00, 0x52, 0x0c, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, 0x52, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x6c, 0x6f, 0x63, 0x6b, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x02, 0x52, 0x04, 0x6c, 0x6f, 0x63, 0x6b, 0x88, 0x01, 0x01, + 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x07, 0x0a, + 0x05, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0xd4, 0x01, 0x0a, 0x1c, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x41, 0x6e, 0x64, 0x44, 0x65, 0x6e, 0x6f, 0x6d, + 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x48, 0x61, 0x73, 0x68, 0x48, 0x00, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, + 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0d, 0x48, 0x01, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x88, 0x01, 0x01, 0x12, 0x27, + 0x0a, 0x0c, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0d, 0x48, 0x02, 0x52, 0x0c, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x6c, 0x6f, 0x63, 0x6b, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x03, 0x52, 0x04, 0x6c, 0x6f, 0x63, 0x6b, 0x88, 0x01, 0x01, + 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x64, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x07, 0x0a, 0x05, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x5b, 0x0a, + 0x15, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x4f, 0x75, 0x74, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x12, 0x42, 0x0a, 0x0a, 0x6f, 0x75, 0x74, 0x5f, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, + 0x41, 0x6e, 0x64, 0x44, 0x65, 0x6e, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x09, 0x6f, 0x75, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x0e, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x55, 0x54, 0x58, 0x4f, 0x12, 0x35, 0x0a, + 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4f, 0x75, 0x74, + 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x88, 0x01, 0x01, 0x12, 0x2c, 0x0a, 0x05, 0x73, 0x75, 0x74, 0x78, 0x6f, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x54, 0x78, 0x4f, 0x75, 0x74, 0x48, 0x01, 0x52, 0x05, 0x73, 0x75, 0x74, 0x78, 0x6f, 0x88, + 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x42, + 0x08, 0x0a, 0x06, 0x5f, 0x73, 0x75, 0x74, 0x78, 0x6f, 0x22, 0x40, 0x0a, 0x0f, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x12, 0x2d, 0x0a, 0x06, + 0x73, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x53, 0x70, 0x65, 0x6e, 0x74, 0x55, + 0x54, 0x58, 0x4f, 0x52, 0x06, 0x73, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x22, 0x1f, 0x0a, 0x09, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x4b, 0x65, 0x79, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x65, 0x79, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x04, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x37, 0x0a, 0x0d, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x52, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4b, 0x65, + 0x79, 0x73, 0x41, 0x6e, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x3c, 0x0a, 0x0f, 0x6b, + 0x65, 0x79, 0x73, 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x4b, 0x65, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0d, 0x6b, 0x65, 0x79, 0x73, + 0x41, 0x6e, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x0f, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x54, 0x72, 0x69, 0x6d, 0x44, 0x65, 0x70, 0x74, 0x68, 0x73, 0x12, 0x47, 0x0a, + 0x0b, 0x74, 0x72, 0x69, 0x6d, 0x5f, 0x64, 0x65, 0x70, 0x74, 0x68, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x54, 0x72, 0x69, 0x6d, 0x44, 0x65, 0x70, 0x74, 0x68, 0x73, 0x2e, 0x54, 0x72, 0x69, 0x6d, 0x44, + 0x65, 0x70, 0x74, 0x68, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x74, 0x72, 0x69, 0x6d, + 0x44, 0x65, 0x70, 0x74, 0x68, 0x73, 0x1a, 0x3d, 0x0a, 0x0f, 0x54, 0x72, 0x69, 0x6d, 0x44, 0x65, + 0x70, 0x74, 0x68, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x61, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x53, 0x65, 0x74, 0x12, 0x4a, 0x0a, 0x12, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x72, 0x72, + 0x61, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, + 0x65, 0x41, 0x72, 0x72, 0x61, 0x79, 0x52, 0x10, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, + 0x69, 0x63, 0x65, 0x41, 0x72, 0x72, 0x61, 0x79, 0x22, 0x6c, 0x0a, 0x15, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x41, 0x72, 0x72, 0x61, + 0x79, 0x12, 0x41, 0x0a, 0x0d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, 0x68, 0x6f, 0x69, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, + 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, + 0x65, 0x48, 0x00, 0x52, 0x0c, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, + 0x73, 0x88, 0x01, 0x01, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x63, + 0x68, 0x6f, 0x69, 0x63, 0x65, 0x73, 0x22, 0x4a, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x43, 0x68, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x71, 0x75, + 0x61, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x71, 0x75, 0x61, 0x69, 0x12, 0x0e, + 0x0a, 0x02, 0x71, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x71, 0x69, 0x12, 0x12, + 0x0a, 0x04, 0x64, 0x69, 0x66, 0x66, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x69, + 0x66, 0x66, 0x22, 0x38, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x42, 0x65, 0x74, 0x61, 0x73, + 0x12, 0x14, 0x0a, 0x05, 0x62, 0x65, 0x74, 0x61, 0x30, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x62, 0x65, 0x74, 0x61, 0x30, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x65, 0x74, 0x61, 0x31, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x65, 0x74, 0x61, 0x31, 0x22, 0x48, 0x0a, 0x0b, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, 0x6f, 0x63, 0x6b, 0x75, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x75, 0x6e, 0x6c, 0x6f, 0x63, 0x6b, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x3c, 0x0a, 0x0c, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, + 0x6f, 0x63, 0x6b, 0x75, 0x70, 0x73, 0x12, 0x2c, 0x0a, 0x07, 0x6c, 0x6f, 0x63, 0x6b, 0x75, 0x70, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, 0x6f, 0x63, 0x6b, 0x75, 0x70, 0x52, 0x07, 0x6c, 0x6f, 0x63, + 0x6b, 0x75, 0x70, 0x73, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x64, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6e, 0x74, 0x2d, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x65, 0x67, 0x69, 0x65, 0x73, 0x2f, 0x67, 0x6f, 0x2d, 0x71, 0x75, 0x61, 0x69, 0x2f, 0x63, + 0x6f, 0x72, 0x65, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -3286,7 +3403,7 @@ func file_core_types_proto_block_proto_rawDescGZIP() []byte { return file_core_types_proto_block_proto_rawDescData } -var file_core_types_proto_block_proto_msgTypes = make([]protoimpl.MessageInfo, 43) +var file_core_types_proto_block_proto_msgTypes = make([]protoimpl.MessageInfo, 45) var file_core_types_proto_block_proto_goTypes = []any{ (*ProtoHeader)(nil), // 0: block.ProtoHeader (*ProtoTransaction)(nil), // 1: block.ProtoTransaction @@ -3323,58 +3440,60 @@ var file_core_types_proto_block_proto_goTypes = []any{ (*ProtoSpentUTXO)(nil), // 32: block.ProtoSpentUTXO (*ProtoSpentUTXOs)(nil), // 33: block.ProtoSpentUTXOs (*ProtoKeys)(nil), // 34: block.ProtoKeys - (*ProtoTrimDepths)(nil), // 35: block.ProtoTrimDepths - (*ProtoTokenChoiceSet)(nil), // 36: block.ProtoTokenChoiceSet - (*ProtoTokenChoiceArray)(nil), // 37: block.ProtoTokenChoiceArray - (*ProtoTokenChoice)(nil), // 38: block.ProtoTokenChoice - (*ProtoBetas)(nil), // 39: block.ProtoBetas - (*ProtoLockup)(nil), // 40: block.ProtoLockup - (*ProtoLockups)(nil), // 41: block.ProtoLockups - nil, // 42: block.ProtoTrimDepths.TrimDepthsEntry - (*common.ProtoHash)(nil), // 43: common.ProtoHash - (*common.ProtoLocation)(nil), // 44: common.ProtoLocation - (*common.ProtoAddress)(nil), // 45: common.ProtoAddress - (*common.ProtoHashes)(nil), // 46: common.ProtoHashes + (*ProtoKeyValue)(nil), // 35: block.ProtoKeyValue + (*ProtoKeysAndValues)(nil), // 36: block.ProtoKeysAndValues + (*ProtoTrimDepths)(nil), // 37: block.ProtoTrimDepths + (*ProtoTokenChoiceSet)(nil), // 38: block.ProtoTokenChoiceSet + (*ProtoTokenChoiceArray)(nil), // 39: block.ProtoTokenChoiceArray + (*ProtoTokenChoice)(nil), // 40: block.ProtoTokenChoice + (*ProtoBetas)(nil), // 41: block.ProtoBetas + (*ProtoLockup)(nil), // 42: block.ProtoLockup + (*ProtoLockups)(nil), // 43: block.ProtoLockups + nil, // 44: block.ProtoTrimDepths.TrimDepthsEntry + (*common.ProtoHash)(nil), // 45: common.ProtoHash + (*common.ProtoLocation)(nil), // 46: common.ProtoLocation + (*common.ProtoAddress)(nil), // 47: common.ProtoAddress + (*common.ProtoHashes)(nil), // 48: common.ProtoHashes } var file_core_types_proto_block_proto_depIdxs = []int32{ - 43, // 0: block.ProtoHeader.parent_hash:type_name -> common.ProtoHash - 43, // 1: block.ProtoHeader.uncle_hash:type_name -> common.ProtoHash - 43, // 2: block.ProtoHeader.evm_root:type_name -> common.ProtoHash - 43, // 3: block.ProtoHeader.tx_hash:type_name -> common.ProtoHash - 43, // 4: block.ProtoHeader.outbound_etx_hash:type_name -> common.ProtoHash - 43, // 5: block.ProtoHeader.etx_rollup_hash:type_name -> common.ProtoHash - 43, // 6: block.ProtoHeader.manifest_hash:type_name -> common.ProtoHash - 43, // 7: block.ProtoHeader.receipt_hash:type_name -> common.ProtoHash - 44, // 8: block.ProtoHeader.location:type_name -> common.ProtoLocation - 43, // 9: block.ProtoHeader.mix_hash:type_name -> common.ProtoHash - 43, // 10: block.ProtoHeader.utxo_root:type_name -> common.ProtoHash - 43, // 11: block.ProtoHeader.etx_set_root:type_name -> common.ProtoHash - 43, // 12: block.ProtoHeader.etx_eligible_slices:type_name -> common.ProtoHash - 43, // 13: block.ProtoHeader.prime_terminus_hash:type_name -> common.ProtoHash - 43, // 14: block.ProtoHeader.interlink_root_hash:type_name -> common.ProtoHash + 45, // 0: block.ProtoHeader.parent_hash:type_name -> common.ProtoHash + 45, // 1: block.ProtoHeader.uncle_hash:type_name -> common.ProtoHash + 45, // 2: block.ProtoHeader.evm_root:type_name -> common.ProtoHash + 45, // 3: block.ProtoHeader.tx_hash:type_name -> common.ProtoHash + 45, // 4: block.ProtoHeader.outbound_etx_hash:type_name -> common.ProtoHash + 45, // 5: block.ProtoHeader.etx_rollup_hash:type_name -> common.ProtoHash + 45, // 6: block.ProtoHeader.manifest_hash:type_name -> common.ProtoHash + 45, // 7: block.ProtoHeader.receipt_hash:type_name -> common.ProtoHash + 46, // 8: block.ProtoHeader.location:type_name -> common.ProtoLocation + 45, // 9: block.ProtoHeader.mix_hash:type_name -> common.ProtoHash + 45, // 10: block.ProtoHeader.utxo_root:type_name -> common.ProtoHash + 45, // 11: block.ProtoHeader.etx_set_root:type_name -> common.ProtoHash + 45, // 12: block.ProtoHeader.etx_eligible_slices:type_name -> common.ProtoHash + 45, // 13: block.ProtoHeader.prime_terminus_hash:type_name -> common.ProtoHash + 45, // 14: block.ProtoHeader.interlink_root_hash:type_name -> common.ProtoHash 5, // 15: block.ProtoTransaction.access_list:type_name -> block.ProtoAccessList - 43, // 16: block.ProtoTransaction.originating_tx_hash:type_name -> common.ProtoHash + 45, // 16: block.ProtoTransaction.originating_tx_hash:type_name -> common.ProtoHash 25, // 17: block.ProtoTransaction.tx_ins:type_name -> block.ProtoTxIns 26, // 18: block.ProtoTransaction.tx_outs:type_name -> block.ProtoTxOuts - 43, // 19: block.ProtoTransaction.parent_hash:type_name -> common.ProtoHash - 43, // 20: block.ProtoTransaction.mix_hash:type_name -> common.ProtoHash + 45, // 19: block.ProtoTransaction.parent_hash:type_name -> common.ProtoHash + 45, // 20: block.ProtoTransaction.mix_hash:type_name -> common.ProtoHash 1, // 21: block.ProtoTransactions.transactions:type_name -> block.ProtoTransaction 0, // 22: block.ProtoHeaders.headers:type_name -> block.ProtoHeader - 43, // 23: block.ProtoManifest.manifest:type_name -> common.ProtoHash + 45, // 23: block.ProtoManifest.manifest:type_name -> common.ProtoHash 15, // 24: block.ProtoAccessList.access_tuples:type_name -> block.ProtoAccessTuple - 43, // 25: block.ProtoWorkObjectHeader.header_hash:type_name -> common.ProtoHash - 43, // 26: block.ProtoWorkObjectHeader.parent_hash:type_name -> common.ProtoHash - 43, // 27: block.ProtoWorkObjectHeader.tx_hash:type_name -> common.ProtoHash - 44, // 28: block.ProtoWorkObjectHeader.location:type_name -> common.ProtoLocation - 43, // 29: block.ProtoWorkObjectHeader.mix_hash:type_name -> common.ProtoHash - 45, // 30: block.ProtoWorkObjectHeader.primary_coinbase:type_name -> common.ProtoAddress + 45, // 25: block.ProtoWorkObjectHeader.header_hash:type_name -> common.ProtoHash + 45, // 26: block.ProtoWorkObjectHeader.parent_hash:type_name -> common.ProtoHash + 45, // 27: block.ProtoWorkObjectHeader.tx_hash:type_name -> common.ProtoHash + 46, // 28: block.ProtoWorkObjectHeader.location:type_name -> common.ProtoLocation + 45, // 29: block.ProtoWorkObjectHeader.mix_hash:type_name -> common.ProtoHash + 47, // 30: block.ProtoWorkObjectHeader.primary_coinbase:type_name -> common.ProtoAddress 6, // 31: block.ProtoWorkObjectHeaders.wo_headers:type_name -> block.ProtoWorkObjectHeader 0, // 32: block.ProtoWorkObjectBody.header:type_name -> block.ProtoHeader 2, // 33: block.ProtoWorkObjectBody.transactions:type_name -> block.ProtoTransactions 7, // 34: block.ProtoWorkObjectBody.uncles:type_name -> block.ProtoWorkObjectHeaders 2, // 35: block.ProtoWorkObjectBody.outbound_etxs:type_name -> block.ProtoTransactions 4, // 36: block.ProtoWorkObjectBody.manifest:type_name -> block.ProtoManifest - 46, // 37: block.ProtoWorkObjectBody.interlink_hashes:type_name -> common.ProtoHashes + 48, // 37: block.ProtoWorkObjectBody.interlink_hashes:type_name -> common.ProtoHashes 6, // 38: block.ProtoWorkObject.wo_header:type_name -> block.ProtoWorkObjectHeader 8, // 39: block.ProtoWorkObject.wo_body:type_name -> block.ProtoWorkObjectBody 1, // 40: block.ProtoWorkObject.tx:type_name -> block.ProtoTransaction @@ -3383,19 +3502,19 @@ var file_core_types_proto_block_proto_depIdxs = []int32{ 11, // 43: block.ProtoWorkObjectBlocksView.work_objects:type_name -> block.ProtoWorkObjectBlockView 9, // 44: block.ProtoWorkObjectHeaderView.work_object:type_name -> block.ProtoWorkObject 9, // 45: block.ProtoWorkObjectShareView.work_object:type_name -> block.ProtoWorkObject - 43, // 46: block.ProtoAccessTuple.storage_key:type_name -> common.ProtoHash + 45, // 46: block.ProtoAccessTuple.storage_key:type_name -> common.ProtoHash 19, // 47: block.ProtoReceiptForStorage.logs:type_name -> block.ProtoLogsForStorage - 43, // 48: block.ProtoReceiptForStorage.tx_hash:type_name -> common.ProtoHash - 45, // 49: block.ProtoReceiptForStorage.contract_address:type_name -> common.ProtoAddress + 45, // 48: block.ProtoReceiptForStorage.tx_hash:type_name -> common.ProtoHash + 47, // 49: block.ProtoReceiptForStorage.contract_address:type_name -> common.ProtoAddress 2, // 50: block.ProtoReceiptForStorage.outbound_etxs:type_name -> block.ProtoTransactions 16, // 51: block.ProtoReceiptsForStorage.receipts:type_name -> block.ProtoReceiptForStorage - 45, // 52: block.ProtoLogForStorage.address:type_name -> common.ProtoAddress - 43, // 53: block.ProtoLogForStorage.topics:type_name -> common.ProtoHash + 47, // 52: block.ProtoLogForStorage.address:type_name -> common.ProtoAddress + 45, // 53: block.ProtoLogForStorage.topics:type_name -> common.ProtoHash 18, // 54: block.ProtoLogsForStorage.logs:type_name -> block.ProtoLogForStorage 9, // 55: block.ProtoPendingHeader.wo:type_name -> block.ProtoWorkObject 21, // 56: block.ProtoPendingHeader.termini:type_name -> block.ProtoTermini - 43, // 57: block.ProtoTermini.dom_termini:type_name -> common.ProtoHash - 43, // 58: block.ProtoTermini.sub_termini:type_name -> common.ProtoHash + 45, // 57: block.ProtoTermini.dom_termini:type_name -> common.ProtoHash + 45, // 58: block.ProtoTermini.sub_termini:type_name -> common.ProtoHash 9, // 59: block.ProtoPendingEtxs.header:type_name -> block.ProtoWorkObject 2, // 60: block.ProtoPendingEtxs.outbound_etxs:type_name -> block.ProtoTransactions 9, // 61: block.ProtoPendingEtxsRollup.header:type_name -> block.ProtoWorkObject @@ -3403,21 +3522,22 @@ var file_core_types_proto_block_proto_depIdxs = []int32{ 27, // 63: block.ProtoTxIns.tx_ins:type_name -> block.ProtoTxIn 29, // 64: block.ProtoTxOuts.tx_outs:type_name -> block.ProtoTxOut 28, // 65: block.ProtoTxIn.previous_out_point:type_name -> block.ProtoOutPoint - 43, // 66: block.ProtoOutPoint.hash:type_name -> common.ProtoHash - 43, // 67: block.ProtoOutPointAndDenomination.hash:type_name -> common.ProtoHash + 45, // 66: block.ProtoOutPoint.hash:type_name -> common.ProtoHash + 45, // 67: block.ProtoOutPointAndDenomination.hash:type_name -> common.ProtoHash 30, // 68: block.ProtoAddressOutPoints.out_points:type_name -> block.ProtoOutPointAndDenomination 28, // 69: block.ProtoSpentUTXO.outpoint:type_name -> block.ProtoOutPoint 29, // 70: block.ProtoSpentUTXO.sutxo:type_name -> block.ProtoTxOut 32, // 71: block.ProtoSpentUTXOs.sutxos:type_name -> block.ProtoSpentUTXO - 42, // 72: block.ProtoTrimDepths.trim_depths:type_name -> block.ProtoTrimDepths.TrimDepthsEntry - 37, // 73: block.ProtoTokenChoiceSet.token_choice_array:type_name -> block.ProtoTokenChoiceArray - 38, // 74: block.ProtoTokenChoiceArray.token_choices:type_name -> block.ProtoTokenChoice - 40, // 75: block.ProtoLockups.lockups:type_name -> block.ProtoLockup - 76, // [76:76] is the sub-list for method output_type - 76, // [76:76] is the sub-list for method input_type - 76, // [76:76] is the sub-list for extension type_name - 76, // [76:76] is the sub-list for extension extendee - 0, // [0:76] is the sub-list for field type_name + 35, // 72: block.ProtoKeysAndValues.keys_and_values:type_name -> block.ProtoKeyValue + 44, // 73: block.ProtoTrimDepths.trim_depths:type_name -> block.ProtoTrimDepths.TrimDepthsEntry + 39, // 74: block.ProtoTokenChoiceSet.token_choice_array:type_name -> block.ProtoTokenChoiceArray + 40, // 75: block.ProtoTokenChoiceArray.token_choices:type_name -> block.ProtoTokenChoice + 42, // 76: block.ProtoLockups.lockups:type_name -> block.ProtoLockup + 77, // [77:77] is the sub-list for method output_type + 77, // [77:77] is the sub-list for method input_type + 77, // [77:77] is the sub-list for extension type_name + 77, // [77:77] is the sub-list for extension extendee + 0, // [0:77] is the sub-list for field type_name } func init() { file_core_types_proto_block_proto_init() } @@ -3442,14 +3562,14 @@ func file_core_types_proto_block_proto_init() { file_core_types_proto_block_proto_msgTypes[29].OneofWrappers = []any{} file_core_types_proto_block_proto_msgTypes[30].OneofWrappers = []any{} file_core_types_proto_block_proto_msgTypes[32].OneofWrappers = []any{} - file_core_types_proto_block_proto_msgTypes[37].OneofWrappers = []any{} + file_core_types_proto_block_proto_msgTypes[39].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_core_types_proto_block_proto_rawDesc, NumEnums: 0, - NumMessages: 43, + NumMessages: 45, NumExtensions: 0, NumServices: 0, }, diff --git a/core/types/proto_block.proto b/core/types/proto_block.proto index 577784fae6..f960795cb0 100644 --- a/core/types/proto_block.proto +++ b/core/types/proto_block.proto @@ -91,6 +91,7 @@ message ProtoWorkObjectHeader { optional bytes prime_terminus_number = 10; optional uint32 lock = 11; optional common.ProtoAddress primary_coinbase = 12; + bytes data = 13; } message ProtoWorkObjectHeaders { @@ -222,6 +223,14 @@ message ProtoKeys { repeated bytes keys = 1; } +message ProtoKeyValue { + bytes key = 1; + bytes value = 2; +} +message ProtoKeysAndValues { + repeated ProtoKeyValue keys_and_values = 1; +} + message ProtoTrimDepths { map trim_depths = 1; } diff --git a/core/types/qi_tx.go b/core/types/qi_tx.go index 29a9c682bb..0a2f4f2826 100644 --- a/core/types/qi_tx.go +++ b/core/types/qi_tx.go @@ -14,6 +14,7 @@ type QiTx struct { TxOut TxOuts `json:"txOuts"` Signature *schnorr.Signature + Data []byte // Data is currently only used for wrapping Qi in the EVM // Work fields ParentHash *common.Hash @@ -103,7 +104,7 @@ func (tx *QiTx) parentHash() *common.Hash { return tx.ParentHash func (tx *QiTx) mixHash() *common.Hash { return tx.MixHash } func (tx *QiTx) workNonce() *BlockNonce { return tx.WorkNonce } func (tx *QiTx) accessList() AccessList { panic("Qi TX does not have accessList") } -func (tx *QiTx) data() []byte { panic("Qi TX does not have data") } +func (tx *QiTx) data() []byte { return tx.Data } func (tx *QiTx) gas() uint64 { panic("Qi TX does not have gas") } func (tx *QiTx) minerTip() *big.Int { panic("Qi TX does not have minerTip") } func (tx *QiTx) gasPrice() *big.Int { panic("Qi TX does not have gasPrice") } diff --git a/core/types/receipt.go b/core/types/receipt.go index a3564d7248..153a6a27d4 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -73,6 +73,10 @@ type Receipt struct { BlockNumber *big.Int `json:"blockNumber,omitempty"` TransactionIndex uint `json:"transactionIndex"` OutboundEtxs Transactions `json:"outboundEtxs"` + + // Cached values: These fields are used to cache values that are expensive to compute and are not stored in the database. + CoinbaseLockupDeletedHashes []*common.Hash + CoinbaseLockupsDeleted map[[47]byte][]byte } type receiptMarshaling struct { diff --git a/core/types/transaction.go b/core/types/transaction.go index a857889871..c1371baaf0 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -54,6 +54,8 @@ const ( DefaultType = iota CoinbaseType ConversionType + CoinbaseLockupType + WrappingQiType ) const ( @@ -221,7 +223,11 @@ func (tx *Transaction) ProtoEncode() (*ProtoTransaction, error) { workNonce := tx.WorkNonce().Uint64() protoTx.WorkNonce = &workNonce } - + if tx.Data() == nil { + protoTx.Data = []byte{} + } else { + protoTx.Data = tx.Data() + } } return protoTx, nil } @@ -372,6 +378,9 @@ func (tx *Transaction) ProtoDecode(protoTx *ProtoTransaction, location common.Lo if protoTx.ChainId == nil { return errors.New("missing required field 'ChainId' in ProtoTransaction") } + if protoTx.Data == nil { + return errors.New("missing required field 'Data' in ProtoTransaction") + } var qiTx QiTx qiTx.ChainID = new(big.Int).SetBytes(protoTx.GetChainId()) @@ -410,6 +419,7 @@ func (tx *Transaction) ProtoDecode(protoTx *ProtoTransaction, location common.Lo nonce := BlockNonce(uint64ToByteArr(*protoTx.WorkNonce)) qiTx.WorkNonce = &nonce } + qiTx.Data = protoTx.GetData() tx.SetInner(&qiTx) default: @@ -453,6 +463,11 @@ func (tx *Transaction) ProtoEncodeTxSigningData() *ProtoTransaction { protoTxSigningData.ChainId = tx.ChainId().Bytes() protoTxSigningData.TxIns, _ = tx.TxIn().ProtoEncode() protoTxSigningData.TxOuts, _ = tx.TxOut().ProtoEncode() + if tx.Data() == nil { + protoTxSigningData.Data = []byte{} + } else { + protoTxSigningData.Data = tx.Data() + } } return protoTxSigningData } @@ -1307,3 +1322,17 @@ func IsConversionTx(tx *Transaction) bool { } return tx.EtxType() == ConversionType } + +func IsQiToQuaiConversionTx(tx *Transaction) bool { + if tx.Type() == ExternalTxType && tx.EtxType() == ConversionType && tx.To().IsInQuaiLedgerScope() { + return true + } + return false +} + +func IsQuaiToQiConversionTx(tx *Transaction) bool { + if tx.Type() == ExternalTxType && tx.EtxType() == ConversionType && tx.To().IsInQiLedgerScope() { + return true + } + return false +} diff --git a/core/types/utxo.go b/core/types/utxo.go index aa8cc89272..f8b6091d54 100644 --- a/core/types/utxo.go +++ b/core/types/utxo.go @@ -20,7 +20,12 @@ const ( MaxTrimDenomination = 5 ) -var MaxQi = new(big.Int).Mul(big.NewInt(math.MaxInt64), big.NewInt(params.Ether)) // This is just a default; determine correct value later +var ( + MaxQi = new(big.Int).Mul(big.NewInt(math.MaxInt64), big.NewInt(params.Ether)) // This is just a default; determine correct value later + QuaiToQiConversionTopic = crypto.Keccak256Hash([]byte("QuaiToQiConversion")) + QuaiCoinbaseLockupTopic = crypto.Keccak256Hash([]byte("QuaiCoinbaseLockup")) + QiCoinbaseLockupTopic = crypto.Keccak256Hash([]byte("QiCoinbaseLockup")) +) // Denominations is a map of denomination to number of Qi var Denominations map[uint8]*big.Int @@ -49,8 +54,8 @@ func init() { TrimDepths = make(map[uint8]uint64) TrimDepths[0] = params.GoldenAgeForkNumberV2 + 720 // 2 hours after fork starts from block 1 TrimDepths[1] = params.GoldenAgeForkNumberV2 + 720 // 2 hours - TrimDepths[2] = params.GoldenAgeForkNumberV2 + 1080 // 3 hours - TrimDepths[3] = params.GoldenAgeForkNumberV2 + 1080 // 3 hours + TrimDepths[2] = params.GoldenAgeForkNumberV2 + 1080 // 3 hours + TrimDepths[3] = params.GoldenAgeForkNumberV2 + 1080 // 3 hours TrimDepths[4] = params.GoldenAgeForkNumberV2 + 2160 // 6 hours TrimDepths[5] = params.GoldenAgeForkNumberV2 + 4320 // 12 hours } @@ -471,3 +476,7 @@ func UTXOHash(txHash common.Hash, index uint16, utxo *UtxoEntry) common.Hash { binary.BigEndian.PutUint16(indexBytes, index) return RlpHash([]interface{}{txHash, indexBytes, utxo}) // TODO: Consider encoding to protobuf instead } + +func CoinbaseLockupHash(ownerContract common.Address, beneficiaryMiner common.Address, delegate common.Address, lockupByte byte, epoch uint32, balance *big.Int, unlockHeight uint32, elements uint16) common.Hash { + return RlpHash([]interface{}{ownerContract, beneficiaryMiner, delegate, lockupByte, epoch, balance, unlockHeight, elements}) // TODO: Consider encoding to protobuf instead +} diff --git a/core/types/wo.go b/core/types/wo.go index 1f8ee03429..e5bedf5023 100644 --- a/core/types/wo.go +++ b/core/types/wo.go @@ -49,6 +49,7 @@ type WorkObjectHeader struct { mixHash common.Hash time uint64 nonce BlockNonce + data []byte lock uint8 PowHash atomic.Value PowDigest atomic.Value @@ -199,6 +200,10 @@ func (wo *WorkObject) PrimaryCoinbase() common.Address { return wo.WorkObjectHeader().PrimaryCoinbase() } +func (wo *WorkObject) Data() []byte { + return wo.WorkObjectHeader().Data() +} + func (wo *WorkObject) QuaiCoinbase() (common.Address, error) { // check if the primary coinbase is in Quai ledger quai := wo.PrimaryCoinbase().IsInQuaiLedgerScope() @@ -440,11 +445,11 @@ func (wo *WorkObject) QiTransactionsWithoutCoinbase() []*Transaction { func (wo *WorkObject) TransactionsWithReceipts() []*Transaction { txs := make([]*Transaction, 0) for _, t := range wo.Transactions() { - if IsCoinBaseTx(t) { + if IsCoinBaseTx(t) && t.To().IsInQiLedgerScope() { // ignore the coinbase tx continue } - if !IsConversionTx(t) && (t.Type() == QuaiTxType || (t.Type() == ExternalTxType && t.To().IsInQuaiLedgerScope())) { + if t.Type() == QuaiTxType || (t.Type() == ExternalTxType && t.Type() != ConversionType && t.To().IsInQuaiLedgerScope()) || IsQuaiToQiConversionTx(t) { txs = append(txs, t) } } @@ -601,6 +606,10 @@ func (wh *WorkObjectHeader) Time() uint64 { return wh.time } +func (wh *WorkObjectHeader) Data() []byte { + return wh.data +} + //////////////////////////////////////////////////////////// /////////////////// Work Object Header Setters /////////////// //////////////////////////////////////////////////////////// @@ -653,6 +662,10 @@ func (wh *WorkObjectHeader) SetTime(val uint64) { wh.time = val } +func (wh *WorkObjectHeader) SetData(val []byte) { + wh.data = val +} + type WorkObjectBody struct { header *Header transactions Transactions @@ -852,7 +865,7 @@ func NewWorkObjectBody(header *Header, txs []*Transaction, etxs []*Transaction, } func NewWorkObjectWithHeader(header *WorkObject, tx *Transaction, nodeCtx int, woType WorkObjectView) *WorkObject { - woHeader := NewWorkObjectHeader(header.Hash(), header.ParentHash(common.ZONE_CTX), header.WorkObjectHeader().number, header.WorkObjectHeader().difficulty, header.WorkObjectHeader().PrimeTerminusNumber(), header.WorkObjectHeader().txHash, header.WorkObjectHeader().nonce, header.WorkObjectHeader().lock, header.WorkObjectHeader().time, header.Location(), header.PrimaryCoinbase()) + woHeader := NewWorkObjectHeader(header.Hash(), header.ParentHash(common.ZONE_CTX), header.WorkObjectHeader().number, header.WorkObjectHeader().difficulty, header.WorkObjectHeader().PrimeTerminusNumber(), header.WorkObjectHeader().txHash, header.WorkObjectHeader().nonce, header.WorkObjectHeader().lock, header.WorkObjectHeader().time, header.Location(), header.PrimaryCoinbase(), header.WorkObjectHeader().data) woBody, _ := NewWorkObjectBody(header.Body().Header(), nil, nil, nil, nil, nil, nil, nodeCtx) return NewWorkObject(woHeader, woBody, tx) } @@ -1026,7 +1039,7 @@ func (wo *WorkObject) ProtoDecode(data *ProtoWorkObject, location common.Locatio return nil } -func NewWorkObjectHeader(headerHash common.Hash, parentHash common.Hash, number *big.Int, difficulty *big.Int, primeTerminusNumber *big.Int, txHash common.Hash, nonce BlockNonce, lock uint8, time uint64, location common.Location, primaryCoinbase common.Address) *WorkObjectHeader { +func NewWorkObjectHeader(headerHash common.Hash, parentHash common.Hash, number *big.Int, difficulty *big.Int, primeTerminusNumber *big.Int, txHash common.Hash, nonce BlockNonce, lock uint8, time uint64, location common.Location, primaryCoinbase common.Address, data []byte) *WorkObjectHeader { return &WorkObjectHeader{ headerHash: headerHash, parentHash: parentHash, @@ -1039,6 +1052,7 @@ func NewWorkObjectHeader(headerHash common.Hash, parentHash common.Hash, number time: time, location: location, primaryCoinbase: primaryCoinbase, + data: data, } } @@ -1056,6 +1070,7 @@ func CopyWorkObjectHeader(wh *WorkObjectHeader) *WorkObjectHeader { cpy.SetPrimeTerminusNumber(wh.primeTerminusNumber) cpy.SetLock(wh.Lock()) cpy.SetPrimaryCoinbase(wh.PrimaryCoinbase()) + cpy.SetData(wh.Data()) return &cpy } @@ -1073,6 +1088,7 @@ func (wh *WorkObjectHeader) RPCMarshalWorkObjectHeader() map[string]interface{} "mixHash": wh.MixHash(), "lock": hexutil.Uint64(wh.Lock()), "primaryCoinbase": wh.PrimaryCoinbase().Hex(), + "data": wh.Data(), } return result } @@ -1116,6 +1132,7 @@ func (wh *WorkObjectHeader) SealEncode() *ProtoWorkObjectHeader { time := wh.Time() lock := uint32(wh.Lock()) coinbase := common.ProtoAddress{Value: wh.PrimaryCoinbase().Bytes()} + data := wh.Data() return &ProtoWorkObjectHeader{ HeaderHash: &hash, @@ -1128,6 +1145,7 @@ func (wh *WorkObjectHeader) SealEncode() *ProtoWorkObjectHeader { Lock: &lock, PrimaryCoinbase: &coinbase, Time: &time, + Data: data, } } @@ -1143,6 +1161,7 @@ func (wh *WorkObjectHeader) ProtoEncode() (*ProtoWorkObjectHeader, error) { mixHash := common.ProtoHash{Value: wh.MixHash().Bytes()} lock := uint32(wh.Lock()) coinbase := common.ProtoAddress{Value: wh.PrimaryCoinbase().Bytes()} + data := wh.Data() return &ProtoWorkObjectHeader{ HeaderHash: &hash, @@ -1157,6 +1176,7 @@ func (wh *WorkObjectHeader) ProtoEncode() (*ProtoWorkObjectHeader, error) { MixHash: &mixHash, Time: &wh.time, PrimaryCoinbase: &coinbase, + Data: data, }, nil } @@ -1177,6 +1197,7 @@ func (wh *WorkObjectHeader) ProtoDecode(data *ProtoWorkObjectHeader, location co wh.SetMixHash(common.BytesToHash(data.GetMixHash().Value)) wh.SetTime(data.GetTime()) wh.SetPrimaryCoinbase(common.BytesToAddress(data.GetPrimaryCoinbase().GetValue(), location)) + wh.SetData(data.GetData()) return nil } diff --git a/core/types/wo_test.go b/core/types/wo_test.go index 6884814957..6a8eab47f7 100644 --- a/core/types/wo_test.go +++ b/core/types/wo_test.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "math/big" "testing" @@ -23,6 +24,7 @@ func woTestData() (*WorkObject, common.Hash) { time: uint64(1), nonce: EncodeNonce(uint64(1)), lock: 0, + data: []byte{0, 1, 2, 3}, }, } return wo, wo.Hash() @@ -30,7 +32,7 @@ func woTestData() (*WorkObject, common.Hash) { func TestWoHash(t *testing.T) { _, actualHash := woTestData() - expectedHash := common.HexToHash("0x572273f26629ca2a02c329cd21dc3def6e1d1934465ea11fca393c7a9ce3c89b") + expectedHash := common.HexToHash("0x5ea71d29dbbfbd3d0ca07fa7349f9275ed9f80376e4077a07ef7d78266b4204a") require.Equal(t, expectedHash, actualHash, "Hash not equal to expected hash") } @@ -104,6 +106,19 @@ func FuzzNonceHash(f *testing.F) { func(woh *WorkObjectHeader, nonce uint64) { woh.nonce = EncodeNonce(nonce) }) } +func FuzzDataHash(f *testing.F) { + header, _ := woTestData() + f.Add(testByte) + f.Add(header.Data()) + f.Fuzz(func(t *testing.T, b []byte) { + localHeader, hash := headerTestData() + if !bytes.Equal(localHeader.extra, b) { + localHeader.extra = b + require.NotEqual(t, localHeader.Hash(), hash, "Hash equal for extra \noriginal: %v, modified: %v", header.Data(), b) + } + }) +} + func fuzzHash(f *testing.F, getField func(*WorkObjectHeader) common.Hash, setField func(*WorkObjectHeader, common.Hash)) { wo, _ := woTestData() f.Add(testByte) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 8b2c2a7a17..90ec801d31 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -26,10 +26,15 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/common/math" + "github.com/dominant-strategies/go-quai/core/rawdb" + "github.com/dominant-strategies/go-quai/core/types" "github.com/dominant-strategies/go-quai/crypto" "github.com/dominant-strategies/go-quai/crypto/blake2b" "github.com/dominant-strategies/go-quai/crypto/bn256" + "github.com/dominant-strategies/go-quai/ethdb" + "github.com/dominant-strategies/go-quai/log" "github.com/dominant-strategies/go-quai/params" + "github.com/holiman/uint256" //lint:ignore SA1019 Needed for precompile "golang.org/x/crypto/ripemd160" @@ -44,8 +49,9 @@ type PrecompiledContract interface { } var ( - PrecompiledContracts map[common.AddressBytes]PrecompiledContract = make(map[common.AddressBytes]PrecompiledContract) - PrecompiledAddresses map[string][]common.Address = make(map[string][]common.Address) + PrecompiledContracts map[common.AddressBytes]PrecompiledContract = make(map[common.AddressBytes]PrecompiledContract) + PrecompiledAddresses map[string][]common.Address = make(map[string][]common.Address) + LockupContractAddresses map[[2]byte]common.Address = make(map[[2]byte]common.Address) // LockupContractAddress is not of type PrecompiledContract ) func InitializePrecompiles(nodeLocation common.Location) { @@ -58,6 +64,7 @@ func InitializePrecompiles(nodeLocation common.Location) { PrecompiledContracts[common.HexToAddressBytes(fmt.Sprintf("0x%02x00000000000000000000000000000000000007", nodeLocation.BytePrefix()))] = &bn256ScalarMul{} PrecompiledContracts[common.HexToAddressBytes(fmt.Sprintf("0x%02x00000000000000000000000000000000000008", nodeLocation.BytePrefix()))] = &bn256Pairing{} PrecompiledContracts[common.HexToAddressBytes(fmt.Sprintf("0x%02x00000000000000000000000000000000000009", nodeLocation.BytePrefix()))] = &blake2F{} + LockupContractAddresses[[2]byte{nodeLocation[0], nodeLocation[1]}] = common.HexToAddress(fmt.Sprintf("0x%02x0000000000000000000000000000000000000A", nodeLocation.BytePrefix()), nodeLocation) for address, _ := range PrecompiledContracts { if address.Location().Equal(nodeLocation) { @@ -67,7 +74,7 @@ func InitializePrecompiles(nodeLocation common.Location) { } } -// ActivePrecompiles returns the precompiles enabled with the current configuration. +// ActivePrecompiles returns the precompiles enabled with the current configuration, except the Lockup Contract. func ActivePrecompiles(rules params.Rules, nodeLocation common.Location) []common.Address { return PrecompiledAddresses[nodeLocation.Name()] } @@ -498,3 +505,426 @@ func intToByteArray20(n uint8) [20]byte { func RequiredGas(input []byte) uint64 { return 0 } + +// RunLockupContract is an API that ties together the Lockup Contract with the EVM. +// The requested function is determined by input length. Contracts should ensure that their input is tightly packed +// with abi.encodePacked. +func RunLockupContract(evm *EVM, ownerContract common.Address, gas *uint64, input []byte) ([]byte, error) { + switch len(input) { + case 60: + if err := UnwrapQi(evm, ownerContract, gas, input); err != nil { + return nil, err + } else { + return []byte{1}, nil + } + case 20: + if err := ClaimQiDeposit(evm, ownerContract, gas, input); err != nil { + return nil, err + } else { + return []byte{1}, nil + } + case 33: + if err := ClaimCoinbaseLockup(evm, ownerContract, evm.Context.BlockNumber.Uint64(), gas, input); err != nil { + return nil, err + } else { + return []byte{1}, nil + } + case 25: + ret, err := GetLockupData(evm, ownerContract, input) + if err != nil { + return nil, err + } else { + return ret, nil + } + case 21: + ret, err := GetLatestLockupData(evm, ownerContract, input) + if err != nil { + return nil, err + } else { + return ret, nil + } + case 0: + epoch, err := evm.GetLatestMinerEpoch() + if err != nil { + return nil, err + } else { + epochBytes := make([]byte, 32) + binary.BigEndian.PutUint32(epochBytes[28:], epoch) // Right-align + return epochBytes, nil + } + default: + return nil, ErrExecutionReverted + } +} + +func GetLockupData(evm *EVM, ownerContract common.Address, input []byte) ([]byte, error) { + if len(input) != 25 { + return nil, errors.New("input length is not 25 bytes") + } + // Extract beneficiaryMiner + beneficiaryMiner := common.BytesToAddress(input[:20], evm.chainConfig.Location) + // Extract lockupByte + lockupByte := input[20] + + // Extract epoch + epoch := binary.BigEndian.Uint32(input[21:25]) + _, err := ownerContract.InternalAndQuaiAddress() + if err != nil { + return nil, err + } + _, err = beneficiaryMiner.InternalAddress() + if err != nil { + return nil, err + } + balance, trancheUnlockHeight, elements, delegate := rawdb.ReadCoinbaseLockup(evm.StateDB.UnderlyingDatabase(), evm.Batch, ownerContract, beneficiaryMiner, lockupByte, epoch) + + return ABIEncodeLockupData(trancheUnlockHeight, balance, elements, delegate) +} + +func GetLatestLockupData(evm *EVM, ownerContract common.Address, input []byte) ([]byte, error) { + if len(input) != 21 { + return nil, errors.New("input length is not 21 bytes") + } + // Extract beneficiaryMiner + beneficiaryMiner := common.BytesToAddress(input[:20], evm.chainConfig.Location) + // Extract lockupByte + lockupByte := input[20] + epoch, err := evm.GetLatestMinerEpoch() + if err != nil { + return nil, err + } + balance, trancheUnlockHeight, elements, delegate := rawdb.ReadCoinbaseLockup(evm.StateDB.UnderlyingDatabase(), evm.Batch, ownerContract, beneficiaryMiner, lockupByte, epoch) + return ABIEncodeLockupData(trancheUnlockHeight, balance, elements, delegate) +} + +func ABIEncodeLockupData(trancheUnlockHeight uint32, balance *big.Int, elements uint16, delegate common.Address) ([]byte, error) { + // Create a buffer for the result + encoded := make([]byte, 0, 128) // 32 bytes for each value + + // Encode trancheUnlockHeight (uint32, right-aligned to 32 bytes) + trancheBytes := make([]byte, 32) + binary.BigEndian.PutUint32(trancheBytes[28:], trancheUnlockHeight) // Right-align + encoded = append(encoded, trancheBytes...) + // Encode balance (32 bytes) + balanceBytes, overflow := uint256.FromBig(balance) + if overflow { + return nil, fmt.Errorf("balance is too large to encode: %v", balance) + } + temp := balanceBytes.Bytes32() + encoded = append(encoded, temp[:]...) + + // Encode elements (uint16, right-aligned to 32 bytes) + elementsBytes := make([]byte, 32) + binary.BigEndian.PutUint16(elementsBytes[30:], elements) // Right-align + encoded = append(encoded, elementsBytes...) + + // Encode delegate (20 bytes, right-aligned to 32 bytes) + delegateBytes := delegate.Bytes() + encoded = append(encoded, common.LeftPadBytes(delegateBytes, 32)...) + + return encoded, nil +} + +func GetAllLockupData(db ethdb.Database, ownerContract, beneficiaryMiner common.Address, location common.Location, logger *log.Logger) (map[string]map[string][]interface{}, error) { + prefix := append(rawdb.CoinbaseLockupPrefix, ownerContract.Bytes()...) + prefix = append(prefix, beneficiaryMiner.Bytes()...) + it := db.NewIterator(prefix, nil) + batch := db.NewBatch() + defer it.Release() + epochToLockupByteToLockupMap := make(map[string]map[string][]interface{}) + for it.Next() { + key := it.Key() + if len(key) != rawdb.CoinbaseLockupKeyLength { + continue + } + _, _, lockupByte, epoch, err := rawdb.ReverseCoinbaseLockupKey(key, location) + if err != nil { + return nil, err + } + balance, trancheUnlockHeight, elements, delegate := rawdb.ReadCoinbaseLockup(db, batch, ownerContract, beneficiaryMiner, lockupByte, epoch) + if trancheUnlockHeight == 0 { + logger.Errorf("lockup is empty: ownerContract=%v, beneficiaryMiner=%v, lockupByte=%v, epoch=%v", ownerContract, beneficiaryMiner, lockupByte, epoch) + } + jsonLockup := map[string]interface{}{ + "balance": balance, + "trancheUnlockHeight": trancheUnlockHeight, + "elements": elements, + "delegate": delegate, + } + if _, ok := epochToLockupByteToLockupMap[fmt.Sprintf("%d", epoch)]; !ok { + epochToLockupByteToLockupMap[fmt.Sprintf("%d", epoch)] = make(map[string][]interface{}) + } + epochToLockupByteToLockupMap[fmt.Sprintf("%d", epoch)][fmt.Sprintf("%d", lockupByte)] = append(epochToLockupByteToLockupMap[fmt.Sprintf("%d", epoch)][fmt.Sprintf("%d", lockupByte)], jsonLockup) + } + return epochToLockupByteToLockupMap, nil +} + +func ClaimCoinbaseLockup(evm *EVM, ownerContract common.Address, currentHeight uint64, gas *uint64, input []byte) error { // Ensure msg.sender is ownerContract + // Input should be tightly packed 33 bytes + if len(input) != 33 { + return errors.New("input length is not 33 bytes") + } + + // Extract beneficiaryMiner + beneficiaryMiner := common.BytesToAddress(input[:20], evm.chainConfig.Location) + // Extract lockupByte + lockupByte := input[20] + + // Extract epoch + epoch := binary.BigEndian.Uint32(input[21:25]) + + // Extract etxGasLimit + etxGasLimit := binary.BigEndian.Uint64(input[25:33]) + + if *gas < etxGasLimit { + return ErrOutOfGas + } + *gas -= etxGasLimit + + _, err := ownerContract.InternalAndQuaiAddress() + if err != nil { + return err + } + _, err = beneficiaryMiner.InternalAddress() + if err != nil { + return err + } + + balance, trancheUnlockHeight, elements, delegate := rawdb.ReadCoinbaseLockup(evm.StateDB.UnderlyingDatabase(), evm.Batch, ownerContract, beneficiaryMiner, lockupByte, epoch) + if trancheUnlockHeight == 0 { + return errors.New("no lockup to claim") + } + if trancheUnlockHeight > uint32(currentHeight) { + return errors.New("tranche is not unlocked yet") + } + if elements == 0 { + return errors.New("no lockup to claim") + } + deletedCoinbaseLockupHash := types.CoinbaseLockupHash(ownerContract, beneficiaryMiner, delegate, lockupByte, epoch, balance, trancheUnlockHeight, elements) + coinbaseLockupKey := rawdb.DeleteCoinbaseLockup(evm.Batch, ownerContract, beneficiaryMiner, lockupByte, epoch) + + evm.ETXCacheLock.RLock() + index := len(evm.ETXCache) + evm.ETXCacheLock.RUnlock() + if index > math.MaxUint16 { + return fmt.Errorf("CreateETX overflow error: too many ETXs in cache") + } + + externalTx := types.ExternalTx{Value: balance, To: &beneficiaryMiner, Sender: ownerContract, EtxType: uint64(types.CoinbaseLockupType), OriginatingTxHash: evm.Hash, ETXIndex: uint16(index), Gas: etxGasLimit} + + evm.ETXCacheLock.Lock() + evm.ETXCache = append(evm.ETXCache, types.NewTx(&externalTx)) + evm.CoinbaseDeletedHashes = append(evm.CoinbaseDeletedHashes, &deletedCoinbaseLockupHash) + if err := rawdb.WriteCoinbaseLockupToMap(evm.CoinbasesDeleted, coinbaseLockupKey, balance, trancheUnlockHeight, elements, delegate); err != nil { + evm.ETXCacheLock.Unlock() + return err + } + evm.ETXCacheLock.Unlock() + return nil +} + +// AddNewLock adds a new locked balance to the lockup contract +func AddNewLock(statedb StateDB, batch ethdb.Batch, ownerContract common.Address, beneficiaryMiner common.Address, delegate common.Address, sender common.InternalAddress, lockupByte byte, unlockHeight uint64, epoch uint32, value *big.Int, location common.Location, log_ bool) ([]byte, []byte, *common.Hash, *common.Hash, error) { + _, err := ownerContract.InternalAndQuaiAddress() + if err != nil { + return nil, nil, nil, nil, err + } + _, err = beneficiaryMiner.InternalAddress() + if err != nil { + return nil, nil, nil, nil, err + } + if sender != common.OneInternal(location) { + return nil, nil, nil, nil, errors.New("sender is not the correct internal address") + } + if value.Sign() == -1 || value.Sign() == 0 { + return nil, nil, nil, nil, errors.New("value is invalid") + } + balance, trancheUnlockHeight, elements, oldDelegate := rawdb.ReadCoinbaseLockup(statedb.UnderlyingDatabase(), batch, ownerContract, beneficiaryMiner, lockupByte, epoch) // delegate can be changed every update if the miner chooses + + oldCoinbaseLockupHash_ := types.CoinbaseLockupHash(ownerContract, beneficiaryMiner, oldDelegate, lockupByte, epoch, balance, trancheUnlockHeight, elements) + oldCoinbaseLockupHashPtr := &oldCoinbaseLockupHash_ + oldKey := rawdb.CoinbaseLockupKey(ownerContract, beneficiaryMiner, lockupByte, epoch) + if trancheUnlockHeight != 0 && unlockHeight < uint64(trancheUnlockHeight) { + return nil, nil, nil, nil, errors.New("new unlock height is less than the current tranche unlock height, math is broken") + } + if epoch == 0 && trancheUnlockHeight != 0 { + return nil, nil, nil, nil, errors.New("epoch is 0 but trancheUnlockHeight is not") + } + + if trancheUnlockHeight == 0 { + // New epoch: create new lockup tranche, don't change previous one + if epoch+1 > math.MaxUint32 { + return nil, nil, nil, nil, errors.New("epoch overflow") + } + elements = 0 + balance = new(big.Int) + trancheUnlockHeight = uint32(unlockHeight) // TODO: ensure overflow is acceptable here + oldCoinbaseLockupHashPtr = nil + oldKey = nil + statedb.Finalise(true) + if log_ { + log.Global.Info("Rotated epoch: ", " owner: ", ownerContract, " miner: ", beneficiaryMiner, " epoch: ", epoch) + } + } + + elements++ + balance.Add(balance, value) + + newKey, err := rawdb.WriteCoinbaseLockup(batch, ownerContract, beneficiaryMiner, lockupByte, epoch, balance, trancheUnlockHeight, elements, delegate) + if err != nil { + return nil, nil, nil, nil, err + } + // Cut off prefix from keys + newKey = newKey[len(rawdb.CoinbaseLockupPrefix):] + + newCoinbaseLockupHash := types.CoinbaseLockupHash(ownerContract, beneficiaryMiner, delegate, lockupByte, epoch, balance, trancheUnlockHeight, elements) + if log_ { + log.Global.Info("Added new lockup: ", " contract: ", ownerContract, " miner: ", beneficiaryMiner, " epoch: ", epoch, " balance: ", balance.String(), " value: ", value.String(), " trancheUnlockHeight: ", trancheUnlockHeight, " elements: ", elements, " lockupByte: ", lockupByte) + } + return oldKey, newKey, oldCoinbaseLockupHashPtr, &newCoinbaseLockupHash, nil +} + +// UnwrapQi is called by a smart contract that owns wrapped Qi to unwrap it for real Qi UTXOs +// It deducts the requested Qi balance from the contract's balance and creates an external transaction to the beneficiary +// It is the responsibility of the contract to ensure solvency in its underyling wrapped Qi balance +func UnwrapQi(evm *EVM, ownerContract common.Address, gas *uint64, input []byte) error { + // input is tightly packed 20 bytes for Qi beneficiary, 32 bytes for value and 8 bytes for etxGasLimit + if len(input) != 60 { + return errors.New("input length is not 36 bytes") + } + beneficiaryQi := common.BytesToAddress(input[:20], evm.chainConfig.Location) + value := new(big.Int).SetBytes(input[20:52]) + etxGasLimit := binary.BigEndian.Uint64(input[52:60]) + + if *gas < etxGasLimit { + return ErrOutOfGas + } + *gas -= etxGasLimit + + lockupContractAddress := LockupContractAddresses[[2]byte{evm.chainConfig.Location[0], evm.chainConfig.Location[1]}] + lockupContractAddressInternal, err := lockupContractAddress.InternalAndQuaiAddress() + if err != nil { + return err + } + + _, err = beneficiaryQi.InternalAndQiAddress() + if err != nil { + return err + } + ownerContractInternal, err := ownerContract.InternalAndQuaiAddress() + if err != nil { + return err + } + ownerContractHash := common.BytesToHash(ownerContractInternal[:]) + + balanceHash := evm.StateDB.GetState(lockupContractAddressInternal, ownerContractHash) + if balanceHash == (common.Hash{}) { + return errors.New("no wrapped Qi balance in contract to unwrap") + } + balanceBig := balanceHash.Big() + if balanceBig.Cmp(value) == -1 { + return fmt.Errorf("not enough wrapped Qi to unwrap: have %v want %v", balanceBig, value) + } + balanceBig.Sub(balanceBig, value) + evm.StateDB.SetState(lockupContractAddressInternal, ownerContractHash, common.BigToHash(balanceBig)) + + evm.ETXCacheLock.RLock() + index := len(evm.ETXCache) + evm.ETXCacheLock.RUnlock() + if index > math.MaxUint16 { + return fmt.Errorf("CreateETX overflow error: too many ETXs in cache") + } + + externalTx := types.ExternalTx{Value: value, To: &beneficiaryQi, Sender: ownerContract, EtxType: uint64(types.CoinbaseLockupType), OriginatingTxHash: evm.Hash, ETXIndex: uint16(index), Gas: etxGasLimit} + + evm.ETXCacheLock.Lock() + evm.ETXCache = append(evm.ETXCache, types.NewTx(&externalTx)) + evm.ETXCacheLock.Unlock() + return nil +} + +// WrapQi is called by the state processor to process an inbound Qi wrapping ETX +// It stores the wrapped Qi balance in the lockup contract keyed with the contract address and provided Quai beneficiary address +// To accept the deposit, the smart contract must call the ClaimQiDeposit function on the precompile +func WrapQi(statedb StateDB, ownerContract, beneficiary common.Address, sender common.InternalAddress, value *big.Int, location common.Location) error { + ownerContractInternal, err := ownerContract.InternalAndQuaiAddress() + if err != nil { + return err + } + beneficiaryInternal, err := beneficiary.InternalAndQuaiAddress() + if err != nil { + return err + } + if sender != common.OneInternal(location) { + return errors.New("sender is not the correct internal address") + } + lockupContractAddress := LockupContractAddresses[[2]byte{location[0], location[1]}] + lockupContractAddressInternal, err := lockupContractAddress.InternalAndQuaiAddress() + if err != nil { + return err + } + + if value.Sign() == -1 { + return errors.New("negative value") + } + if value.Sign() == 0 { + return nil + } + wrappedQiKey := common.Hash{} + copy(wrappedQiKey[:16], ownerContractInternal[:16]) + copy(wrappedQiKey[16:], beneficiaryInternal[:16]) + balanceHash := statedb.GetState(lockupContractAddressInternal, wrappedQiKey) + if (balanceHash == common.Hash{}) { + statedb.SetState(lockupContractAddressInternal, wrappedQiKey, common.BigToHash(value)) + } else { + balanceBig := balanceHash.Big() + balanceBig.Add(balanceBig, value) + statedb.SetState(lockupContractAddressInternal, wrappedQiKey, common.BigToHash(balanceBig)) + } + return nil +} + +// ClaimQiDeposit is called by the owner smart contract to claim a wrapped Qi deposit +// It adds the wrapped Qi balance to the smart contract's Wrapped Qi balance +// The contract should then mint the equivalent amount of Wrapped Qi tokens to the Quai beneficiary +func ClaimQiDeposit(evm *EVM, ownerContract common.Address, gas *uint64, input []byte) error { + // input is tightly packed 20 bytes for Quai owner + if len(input) != 20 { + return errors.New("input length is not 20 bytes") + } + quaiOwner := common.BytesToAddress(input, evm.chainConfig.Location) + lockupContractAddress := LockupContractAddresses[[2]byte{evm.chainConfig.Location[0], evm.chainConfig.Location[1]}] + lockupContractAddressInternal, err := lockupContractAddress.InternalAndQuaiAddress() + if err != nil { + return err + } + ownerContractInternal, err := ownerContract.InternalAndQuaiAddress() + if err != nil { + return err + } + ownerQuaiInternal, err := quaiOwner.InternalAndQuaiAddress() + if err != nil { + return err + } + + wrappedQiKey := common.Hash{} + copy(wrappedQiKey[:16], ownerContractInternal[:16]) + copy(wrappedQiKey[16:], ownerQuaiInternal[:16]) + balanceHash := evm.StateDB.GetState(lockupContractAddressInternal, wrappedQiKey) + if balanceHash == (common.Hash{}) { + return errors.New("no wrapped Qi balance to claim") + } + evm.StateDB.SetState(lockupContractAddressInternal, wrappedQiKey, common.Hash{}) + + ownerContractHash := common.BytesToHash(ownerContractInternal[:]) + ownerContractBalanceHash := evm.StateDB.GetState(lockupContractAddressInternal, ownerContractHash) + if ownerContractBalanceHash == (common.Hash{}) { + evm.StateDB.SetState(lockupContractAddressInternal, ownerContractHash, balanceHash) + } else { + ownerContractBalance := ownerContractBalanceHash.Big() + ownerContractBalance.Add(ownerContractBalance, balanceHash.Big()) + evm.StateDB.SetState(lockupContractAddressInternal, ownerContractHash, common.BigToHash(ownerContractBalance)) + } + + return nil +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 26318ab20f..17d1340798 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -28,6 +28,9 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/core/types" "github.com/dominant-strategies/go-quai/crypto" + "github.com/dominant-strategies/go-quai/ethdb" + "github.com/dominant-strategies/go-quai/ethdb/memorydb" + "github.com/dominant-strategies/go-quai/log" "github.com/dominant-strategies/go-quai/params" "github.com/holiman/uint256" ) @@ -139,21 +142,31 @@ type EVM struct { // applied in opCall*. callGasTemp uint64 - ETXCache []*types.Transaction - ETXCacheLock sync.RWMutex + ETXCache []*types.Transaction + CoinbaseDeletedHashes []*common.Hash + CoinbasesDeleted map[[47]byte][]byte + ETXCacheLock sync.RWMutex + Batch ethdb.Batch } // NewEVM returns a new EVM. The returned EVM is not thread safe and should // only ever be used *once*. -func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM { +func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config, batch ethdb.Batch) *EVM { evm := &EVM{ - Context: blockCtx, - TxContext: txCtx, - StateDB: statedb, - Config: config, - chainConfig: chainConfig, - chainRules: chainConfig.Rules(blockCtx.BlockNumber), - ETXCache: make([]*types.Transaction, 0), + Context: blockCtx, + TxContext: txCtx, + StateDB: statedb, + Config: config, + chainConfig: chainConfig, + chainRules: chainConfig.Rules(blockCtx.BlockNumber), + ETXCache: make([]*types.Transaction, 0), + CoinbaseDeletedHashes: make([]*common.Hash, 0), + CoinbasesDeleted: make(map[[47]byte][]byte), + } + if batch != nil { + evm.Batch = batch + } else { + evm.Batch = memorydb.New(log.Global).NewBatch() // Just used as a cache for simulating calls } evm.interpreter = NewEVMInterpreter(evm, config) return evm @@ -164,6 +177,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { evm.TxContext = txCtx evm.StateDB = statedb + evm.ResetCoinbasesDeleted() } // Cancel cancels any running EVM operation. This may be called concurrently and @@ -198,6 +212,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, 0, ErrInsufficientBalance } + if addr.Equal(LockupContractAddresses[[2]byte(evm.chainConfig.Location)]) { + ret, err := RunLockupContract(evm, caller.Address(), &gas, input) + return ret, gas, 0, err + } snapshot := evm.StateDB.Snapshot() p, isPrecompile, addr := evm.precompile(addr) internalAddr, err := addr.InternalAndQuaiAddress() @@ -689,3 +707,17 @@ func calcEtxFeeMultiplier(fromAddr, toAddr common.Address) *big.Int { // ChainConfig returns the environment's chain configuration func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } + +func (evm *EVM) GetLatestMinerEpoch() (uint32, error) { return evm.StateDB.GetLatestEpoch() } + +func (evm *EVM) ResetCoinbasesDeleted() { + evm.CoinbasesDeleted = make(map[[47]byte][]byte) + evm.CoinbaseDeletedHashes = make([]*common.Hash, 0) +} + +func (evm *EVM) UndoCoinbasesDeleted() { + for key, value := range evm.CoinbasesDeleted { + evm.Batch.Put(key[:], value) + } + evm.ResetCoinbasesDeleted() +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 3e14591ca9..05aca55ad6 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -885,6 +885,7 @@ func opETX(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte addr, value, etxGasLimit, gasTipCap, gasFeeCap, inOffset, inSize, accessListOffset, accessListSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() toAddr := common.Bytes20ToAddress(addr.Bytes20(), interpreter.evm.chainConfig.Location) // Verify address is not in context + // This means that a conversion cannot happen with opETX if common.IsInChainScope(toAddr.Bytes(), interpreter.evm.chainConfig.Location) { temp.Clear() stack.push(&temp) @@ -911,6 +912,21 @@ func opETX(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte return nil, nil } + if etxGasLimit.CmpUint64(math.MaxUint64) == 1 { + temp.Clear() + stack.push(&temp) + fmt.Printf("%x opETX error: gas limit %d is greater than maximum %d\n", scope.Contract.self.Address(), etxGasLimit, math.MaxInt64) + return nil, nil + } + + // Overflow not a problem here as overflow guarantees a number larger than txgas + if etxGasLimit.Uint64() < params.TxGas { + temp.Clear() + stack.push(&temp) + fmt.Printf("%x opETX error: gas limit %d is less than minimum %d\n", scope.Contract.self.Address(), etxGasLimit, params.TxGas) + return nil, nil + } + interpreter.evm.StateDB.SubBalance(internalSender, total.ToBig()) // Get the arguments from the memory. @@ -1004,6 +1020,14 @@ func opConvert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] return nil, nil } + // Overflow not a problem here as overflow guarantees a number larger than txgas + if etxGasLimit.Uint64() < params.TxGas { + temp.Clear() + stack.push(&temp) + fmt.Printf("%x opETX error: gas limit %d is less than minimum %d\n", scope.Contract.self.Address(), etxGasLimit, params.TxGas) + return nil, nil + } + interpreter.evm.StateDB.SubBalance(internalSender, total.ToBig()) interpreter.evm.ETXCacheLock.RLock() diff --git a/core/vm/interface.go b/core/vm/interface.go index 53673a0106..45af3a3396 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -21,6 +21,7 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/core/types" + "github.com/dominant-strategies/go-quai/ethdb" ) // StateDB is an EVM database for full state querying. @@ -75,6 +76,11 @@ type StateDB interface { AddPreimage(common.Hash, []byte) ForEachStorage(common.InternalAddress, func(common.Hash, common.Hash) bool) error + UnderlyingDatabase() ethdb.KeyValueReader + + GetLatestEpoch() (uint32, error) + SetLatestEpoch(epoch uint32) error + Finalise(deleteEmptyObjects bool) } // CallContext provides a basic interface for the EVM calling conventions. The EVM diff --git a/core/worker.go b/core/worker.go index 1b28b566e1..2730068310 100644 --- a/core/worker.go +++ b/core/worker.go @@ -23,6 +23,7 @@ import ( "github.com/dominant-strategies/go-quai/core/rawdb" "github.com/dominant-strategies/go-quai/core/state" "github.com/dominant-strategies/go-quai/core/types" + "github.com/dominant-strategies/go-quai/core/vm" "github.com/dominant-strategies/go-quai/ethdb" "github.com/dominant-strategies/go-quai/event" "github.com/dominant-strategies/go-quai/log" @@ -53,12 +54,15 @@ const ( c_uncleCacheSize = 100 ) +var defaultLockupContractAddress = common.HexToAddress("0x004A2e19E838218544eE571a900f62Cb050B39CE", common.Location{0, 0}) + // environment is the worker's current environment and holds all // information of the sealing block generation. type environment struct { signer types.Signer state *state.StateDB // apply state changes here + batch ethdb.Batch // batch to write UTXO and coinbase lockup changes (in memory) ancestors mapset.Set // ancestor set (used for checking uncle parent validity) family mapset.Set // family set (used for checking uncle invalidity) tcount int // tx count in cycle @@ -80,11 +84,15 @@ type environment struct { uncles map[common.Hash]*types.WorkObjectHeader utxosCreate []common.Hash utxosDelete []common.Hash + coinbaseLockupsCreated map[string]common.Hash + coinbaseLockupsDeleted map[string]common.Hash + coinbaseRotatedEpochs map[string]struct{} parentStateSize *big.Int quaiCoinbaseEtxs map[[21]byte]*big.Int deletedUtxos map[common.Hash]struct{} qiGasScalingFactor float64 utxoSetSize uint64 + coinbaseLatestEpoch uint32 } // unclelist returns the contained uncles as the list format. @@ -631,7 +639,8 @@ func (w *worker) GeneratePendingHeader(block *types.WorkObject, fill bool, txs t if err != nil { return nil, err } - lockupByte := work.wo.Lock() + //lockupByte := work.wo.Lock() + lockupData := work.wo.Data() // If the primary coinbase belongs to a ledger and there is no fees // for other ledger, there is no etxs emitted for the other ledger @@ -639,22 +648,23 @@ func (w *worker) GeneratePendingHeader(block *types.WorkObject, fill bool, txs t coinbaseReward := misc.CalculateReward(block, work.wo.WorkObjectHeader()) blockReward := new(big.Int).Add(coinbaseReward, work.quaiFees) primaryCoinbase := w.GetPrimaryCoinbase() - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: primaryCoinbase, Data: []byte{lockupByte}}) + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: primaryCoinbase, Data: lockupData}) work.etxs = append(work.etxs, coinbaseEtx) if work.utxoFees.Cmp(big.NewInt(0)) != 0 { secondaryCoinbase := w.GetSecondaryCoinbase() - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: work.utxoFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: w.secondaryCoinbase, Data: []byte{lockupByte}}) + // TODO: Perhaps it makes more sense not to send fee rewards to the lockup contract? + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: work.utxoFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: w.secondaryCoinbase, Data: lockupData}) work.etxs = append(work.etxs, coinbaseEtx) } } else if bytes.Equal(work.wo.PrimaryCoinbase().Bytes(), qiCoinbase.Bytes()) { coinbaseReward := misc.CalculateReward(block, work.wo.WorkObjectHeader()) blockReward := new(big.Int).Add(coinbaseReward, work.utxoFees) primaryCoinbase := w.GetPrimaryCoinbase() - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: primaryCoinbase, Data: []byte{lockupByte}}) + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &primaryCoinbase, Gas: params.TxGas, Value: blockReward, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQi(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: primaryCoinbase, Data: lockupData}) work.etxs = append(work.etxs, coinbaseEtx) if work.quaiFees.Cmp(big.NewInt(0)) != 0 { secondaryCoinbase := w.GetSecondaryCoinbase() - coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: work.quaiFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: secondaryCoinbase, Data: []byte{lockupByte}}) + coinbaseEtx := types.NewTx(&types.ExternalTx{To: &secondaryCoinbase, Gas: params.TxGas, Value: work.quaiFees, EtxType: types.CoinbaseType, OriginatingTxHash: common.SetBlockHashForQuai(block.Hash(), w.hc.NodeLocation()), ETXIndex: uint16(len(work.etxs)), Sender: secondaryCoinbase, Data: lockupData}) work.etxs = append(work.etxs, coinbaseEtx) } } @@ -669,7 +679,7 @@ func (w *worker) GeneratePendingHeader(block *types.WorkObject, fill bool, txs t } else { originHash = common.SetBlockHashForQi(block.Hash(), w.hc.NodeLocation()) } - work.etxs = append(work.etxs, types.NewTx(&types.ExternalTx{To: &uncleCoinbase, Gas: params.TxGas, Value: reward, EtxType: types.CoinbaseType, OriginatingTxHash: originHash, ETXIndex: uint16(len(work.etxs)), Sender: uncleCoinbase, Data: []byte{uncle.Lock()}})) + work.etxs = append(work.etxs, types.NewTx(&types.ExternalTx{To: &uncleCoinbase, Gas: params.TxGas, Value: reward, EtxType: types.CoinbaseType, OriginatingTxHash: originHash, ETXIndex: uint16(len(work.etxs)), Sender: uncleCoinbase, Data: uncle.Data()})) } } @@ -722,6 +732,7 @@ func (w *worker) GeneratePendingHeader(block *types.WorkObject, fill bool, txs t } if nodeCtx == common.ZONE_CTX && w.hc.ProcessingState() { + work.batch.Reset() if !fromOrderedTransactionSet { select { case w.orderTransactionCh <- transactionOrderingInfo{work.txs, work.gasUsedAfterTransaction, block}: @@ -729,6 +740,9 @@ func (w *worker) GeneratePendingHeader(block *types.WorkObject, fill bool, txs t w.logger.Debug("w.orderTranscationCh is full") } } + for _, hash := range work.coinbaseLockupsCreated { + work.utxosCreate = append(work.utxosCreate, hash) + } } // Create a local environment copy, avoid the data race with snapshot state. @@ -742,6 +756,9 @@ func (w *worker) GeneratePendingHeader(block *types.WorkObject, fill bool, txs t w.printPendingHeaderInfo(work, newWo, start) work.utxosCreate = nil work.utxosDelete = nil + work.coinbaseLockupsCreated = nil + work.coinbaseLockupsDeleted = nil + work.coinbaseRotatedEpochs = nil return newWo, nil } @@ -994,22 +1011,36 @@ func (w *worker) makeEnv(parent *types.WorkObject, proposedWo *types.WorkObject, } // Note the passed coinbase may be different with header.Coinbase. env := &environment{ - signer: types.MakeSigner(w.chainConfig, proposedWo.Number(w.hc.NodeCtx())), - state: state, - primaryCoinbase: primaryCoinbase, - secondaryCoinbase: secondaryCoinbase, - ancestors: mapset.NewSet(), - family: mapset.NewSet(), - wo: proposedWo, - uncles: make(map[common.Hash]*types.WorkObjectHeader), - etxRLimit: etxRLimit, - etxPLimit: etxPLimit, - parentStateSize: quaiStateSize, - quaiCoinbaseEtxs: make(map[[21]byte]*big.Int), - deletedUtxos: make(map[common.Hash]struct{}), - qiGasScalingFactor: math.Log(float64(utxoSetSize)), - utxoSetSize: utxoSetSize, + signer: types.MakeSigner(w.chainConfig, proposedWo.Number(w.hc.NodeCtx())), + state: state, + batch: w.workerDb.NewBatch(), + primaryCoinbase: primaryCoinbase, + secondaryCoinbase: secondaryCoinbase, + ancestors: mapset.NewSet(), + family: mapset.NewSet(), + wo: proposedWo, + uncles: make(map[common.Hash]*types.WorkObjectHeader), + etxRLimit: etxRLimit, + etxPLimit: etxPLimit, + parentStateSize: quaiStateSize, + quaiCoinbaseEtxs: make(map[[21]byte]*big.Int), + deletedUtxos: make(map[common.Hash]struct{}), + qiGasScalingFactor: math.Log(float64(utxoSetSize)), + utxoSetSize: utxoSetSize, + coinbaseLockupsCreated: make(map[string]common.Hash), + coinbaseLockupsDeleted: make(map[string]common.Hash), + coinbaseRotatedEpochs: make(map[string]struct{}), + } + coinbaseLockupEpoch, err := env.state.GetLatestEpoch() + if err != nil { + return nil, fmt.Errorf("could not get latest epoch: %w", err) + } + if proposedWo.NumberU64(common.ZONE_CTX)%params.CoinbaseEpochBlocks == 0 || coinbaseLockupEpoch == 0 { + coinbaseLockupEpoch++ + env.state.SetLatestEpoch(coinbaseLockupEpoch) } + env.coinbaseLatestEpoch = coinbaseLockupEpoch + env.batch.SetPending(true) // Keep track of transactions which return errors so they can be removed env.tcount = 0 return env, nil @@ -1092,58 +1123,242 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t return nil, false, nil } } - if tx.To().IsInQiLedgerScope() { - var lockup *big.Int - // The first lock up period changes after the fork - if lockupByte == 0 { - if env.wo.NumberU64(common.ZONE_CTX) < params.GoldenAgeForkNumberV1 { - lockup = new(big.Int).SetUint64(params.OldConversionLockPeriod) - if lockup.Uint64() < params.OldConversionLockPeriod { - return nil, false, fmt.Errorf("coinbase lockup period is less than the minimum lockup period of %d blocks", params.OldConversionLockPeriod) + if tx.To().IsInQiLedgerScope() { // Qi coinbase + + lockup := new(big.Int).SetUint64(params.LockupByteToBlockDepth[lockupByte]) + if lockup.Uint64() < params.OldConversionLockPeriod { + return nil, false, fmt.Errorf("coinbase lockup period is less than the minimum lockup period of %d blocks", params.OldConversionLockPeriod) + } + lockup.Add(lockup, env.wo.Number(w.hc.NodeCtx())) + value := params.CalculateCoinbaseValueWithLockup(tx.Value(), lockupByte) + if len(tx.Data()) == 1 { + denominations := misc.FindMinDenominations(value) + outputIndex := uint16(0) + // Iterate over the denominations in descending order + for denomination := types.MaxDenomination; denomination >= 0; denomination-- { + // If the denomination count is zero, skip it + if denominations[uint8(denomination)] == 0 { + continue } + for j := uint64(0); j < denominations[uint8(denomination)]; j++ { + if outputIndex >= types.MaxOutputIndex { + // No more gas, the rest of the denominations are lost but the tx is still valid + break + } + // the ETX hash is guaranteed to be unique + utxoHash := types.UTXOHash(tx.Hash(), outputIndex, types.NewUtxoEntry(types.NewTxOut(uint8(denomination), tx.To().Bytes(), lockup))) + env.utxosCreate = append(env.utxosCreate, utxoHash) + outputIndex++ + } + } + receipt := &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: 0, TxHash: tx.Hash()} + gasUsed := env.wo.GasUsed() + if parent.NumberU64(common.ZONE_CTX) >= params.TimeToStartTx { + gasUsed += params.TxGas + } + + env.wo.Header().SetGasUsed(gasUsed) + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, gasUsed) + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) + return receipt.Logs, true, nil + } else if len(tx.Data()) == common.AddressLength+1 || len(tx.Data()) == common.AddressLength+common.AddressLength+1 { + contractAddr := common.BytesToAddress(tx.Data()[1:common.AddressLength+1], w.chainConfig.Location) + internal, err := contractAddr.InternalAndQuaiAddress() + if err != nil { + return nil, false, fmt.Errorf("coinbase tx %x has invalid recipient: %w", tx.Hash(), err) + } + if env.state.GetCode(internal) == nil { + // Coinbase data is either too long or too small + // Coinbase reward is lost + receipt := &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusFailed, GasUsed: 0, TxHash: tx.Hash()} + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, 0) + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) + return []*types.Log{}, false, nil + } + var delegate common.Address + if len(tx.Data()) == common.AddressLength+common.AddressLength+1 { + delegate = common.BytesToAddress(tx.Data()[common.AddressLength+1:], w.chainConfig.Location) } else { - lockup = new(big.Int).SetUint64(params.NewConversionLockPeriod) - if lockup.Uint64() < params.NewConversionLockPeriod { - return nil, false, fmt.Errorf("coinbase lockup period is less than the minimum lockup period of %d blocks", params.NewConversionLockPeriod) + delegate = common.Zero + } + oldCoinbaseLockupKey, newCoinbaseLockupKey, oldCoinbaseLockupHash, newCoinbaseLockupHash, err := vm.AddNewLock(env.state, env.batch, contractAddr, *tx.To(), delegate, common.OneInternal(w.chainConfig.Location), lockupByte, lockup.Uint64(), env.coinbaseLatestEpoch, value, w.chainConfig.Location, false) + if err != nil || newCoinbaseLockupHash == nil { + return nil, false, fmt.Errorf("could not add new lock: %w", err) + } + // Store the new lockup key every time + env.coinbaseLockupsCreated[string(newCoinbaseLockupKey)] = *newCoinbaseLockupHash + + if oldCoinbaseLockupHash != nil { + // We deleted (updated) the old lockup, write it to deleted list but only the first time + if _, exists := env.coinbaseLockupsDeleted[string(oldCoinbaseLockupKey)]; !exists { + if _, exists := env.coinbaseRotatedEpochs[string(newCoinbaseLockupKey)]; !exists { + env.coinbaseLockupsDeleted[string(oldCoinbaseLockupKey)] = *oldCoinbaseLockupHash + env.utxosDelete = append(env.utxosDelete, *oldCoinbaseLockupHash) + } } + } else { + // If we did not delete, we are rotating the epoch and need to store it + env.coinbaseRotatedEpochs[string(newCoinbaseLockupKey)] = struct{}{} + } + receipt := &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: 0, TxHash: tx.Hash()} // todo: consider adding the reward to the receipt in a log + gasUsed := env.wo.GasUsed() + if parent.NumberU64(common.ZONE_CTX) >= params.TimeToStartTx { + gasUsed += params.TxGas } + env.wo.Header().SetGasUsed(gasUsed) + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, gasUsed) + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) + return receipt.Logs, true, nil } else { - lockup = new(big.Int).SetUint64(params.LockupByteToBlockDepth[lockupByte]) + // Coinbase data is either too long or too small + // Coinbase reward is lost + receipt := &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusFailed, GasUsed: 0, TxHash: tx.Hash()} + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, 0) + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) + return []*types.Log{}, false, nil + } + + } else if tx.To().IsInQuaiLedgerScope() { // Quai coinbase + _, err := tx.To().InternalAndQuaiAddress() + if err != nil { + return nil, false, fmt.Errorf("coinbase tx %x has invalid recipient: %w", tx.Hash(), err) + } + var receipt *types.Receipt + if len(tx.Data()) == 1 { + // Coinbase has no extra data + // Coinbase is valid, no gas used + receipt = &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: 0, TxHash: tx.Hash()} + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, 0) + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) + return []*types.Log{}, false, nil + } else if len(tx.Data()) != common.AddressLength+1 && len(tx.Data()) != common.AddressLength+common.AddressLength+1 { + // Coinbase data is either too long or too small + // Coinbase reward is lost + receipt = &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusFailed, GasUsed: 0, TxHash: tx.Hash()} + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, 0) + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) + return []*types.Log{}, false, nil + } + // Create params for uint256 lockup, uint256 balance, address recipient + lockup := new(big.Int).SetUint64(params.LockupByteToBlockDepth[lockupByte]) + if lockup.Uint64() < params.OldConversionLockPeriod { + return nil, false, fmt.Errorf("coinbase lockup period is less than the minimum lockup period of %d blocks", params.OldConversionLockPeriod) } lockup.Add(lockup, env.wo.Number(w.hc.NodeCtx())) - value := params.CalculateCoinbaseValueWithLockup(tx.Value(), lockupByte) - denominations := misc.FindMinDenominations(value) - outputIndex := uint16(0) - // Iterate over the denominations in descending order - for denomination := types.MaxDenomination; denomination >= 0; denomination-- { - // If the denomination count is zero, skip it - if denominations[uint8(denomination)] == 0 { - continue - } - for j := uint64(0); j < denominations[uint8(denomination)]; j++ { - if outputIndex >= types.MaxOutputIndex { - // No more gas, the rest of the denominations are lost but the tx is still valid - break + contractAddr := common.BytesToAddress(tx.Data()[1:], w.chainConfig.Location) + internal, err := contractAddr.InternalAndQuaiAddress() + if err != nil { + return nil, false, fmt.Errorf("coinbase tx %x has invalid recipient: %w", tx.Hash(), err) + } + + if env.state.GetCode(internal) == nil { + // Coinbase data is either too long or too small + // Coinbase reward is lost + receipt := &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusFailed, GasUsed: 0, TxHash: tx.Hash()} + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, 0) + env.txs = append(env.txs, tx) + env.receipts = append(env.receipts, receipt) + return []*types.Log{}, false, nil + } + var delegate common.Address + if len(tx.Data()) == common.AddressLength+common.AddressLength+1 { + delegate = common.BytesToAddress(tx.Data()[common.AddressLength+1:], w.chainConfig.Location) + } else { + delegate = common.Zero + } + reward := params.CalculateCoinbaseValueWithLockup(tx.Value(), lockupByte) + // Add the lockup owned by the smart contract with the miner as beneficiary + oldCoinbaseLockupKey, newCoinbaseLockupKey, oldCoinbaseLockupHash, newCoinbaseLockupHash, err := vm.AddNewLock(env.state, env.batch, contractAddr, *tx.To(), delegate, common.OneInternal(w.chainConfig.Location), lockupByte, lockup.Uint64(), env.coinbaseLatestEpoch, reward, w.chainConfig.Location, false) + if err != nil || newCoinbaseLockupHash == nil { + return nil, false, fmt.Errorf("could not add new lock: %w", err) + } + // Store the new lockup key every time + env.coinbaseLockupsCreated[string(newCoinbaseLockupKey)] = *newCoinbaseLockupHash + + if oldCoinbaseLockupHash != nil { + // We deleted (updated) the old lockup, write it to deleted list but only the first time + if _, exists := env.coinbaseLockupsDeleted[string(oldCoinbaseLockupKey)]; !exists { + if _, exists := env.coinbaseRotatedEpochs[string(newCoinbaseLockupKey)]; !exists { + env.coinbaseLockupsDeleted[string(oldCoinbaseLockupKey)] = *oldCoinbaseLockupHash + env.utxosDelete = append(env.utxosDelete, *oldCoinbaseLockupHash) } - // the ETX hash is guaranteed to be unique - utxoHash := types.UTXOHash(tx.Hash(), outputIndex, types.NewUtxoEntry(types.NewTxOut(uint8(denomination), tx.To().Bytes(), lockup))) - env.utxosCreate = append(env.utxosCreate, utxoHash) - outputIndex++ } + } else { + // If we did not delete, we are rotating the epoch and need to store it + env.coinbaseRotatedEpochs[string(newCoinbaseLockupKey)] = struct{}{} } - } - gasUsed := env.wo.GasUsed() - if parent.NumberU64(common.ZONE_CTX) >= params.TimeToStartTx { - gasUsed += params.TxGas - } - env.wo.Header().SetGasUsed(gasUsed) - env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, gasUsed) - env.txs = append(env.txs, tx) + receipt = &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: 0, TxHash: tx.Hash()} // todo: consider adding the reward to the receipt in a log - return []*types.Log{}, false, nil + gasUsed := env.wo.GasUsed() + if parent.NumberU64(common.ZONE_CTX) >= params.TimeToStartTx { + gasUsed += params.TxGas + } + env.wo.Header().SetGasUsed(gasUsed) + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, gasUsed) + env.receipts = append(env.receipts, receipt) + env.txs = append(env.txs, tx) + + return receipt.Logs, true, nil + } } if tx.Type() == types.ExternalTxType && tx.To().IsInQiLedgerScope() { gasUsed := env.wo.GasUsed() + if tx.EtxType() == types.CoinbaseLockupType { + // This is either an unlocked Qi coinbase that was redeemed or Wrapped Qi + // An unlocked/redeemed Quai coinbase ETX is processed below as a standard Quai ETX + if tx.To().IsInQiLedgerScope() { + txGas := tx.Gas() + denominations := misc.FindMinDenominations(tx.Value()) + total := big.NewInt(0) + outputIndex := uint16(0) + success := true + // Iterate over the denominations in descending order + for denomination := types.MaxDenomination; denomination >= 0; denomination-- { + // If the denomination count is zero, skip it + if denominations[uint8(denomination)] == 0 { + continue + } + + for j := uint64(0); j < denominations[uint8(denomination)]; j++ { + if txGas < params.CallValueTransferGas || outputIndex >= types.MaxOutputIndex { + // No more gas, the rest of the denominations are lost but the tx is still valid + success = false + break + } + txGas -= params.CallValueTransferGas + if err := env.gasPool.SubGas(params.CallValueTransferGas); err != nil { + return nil, false, err + } + gasUsed += params.CallValueTransferGas + utxo := types.NewUtxoEntry(types.NewTxOut(uint8(denomination), tx.To().Bytes(), big.NewInt(0))) + env.utxosCreate = append(env.utxosCreate, types.UTXOHash(tx.Hash(), outputIndex, utxo)) + total.Add(total, types.Denominations[uint8(denomination)]) + outputIndex++ + } + } + receipt := &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: tx.Gas() - txGas, TxHash: tx.Hash(), + Logs: []*types.Log{{ + Address: *tx.To(), + Topics: []common.Hash{types.QuaiToQiConversionTopic}, + Data: total.Bytes(), + }}, + } + if !success { + receipt.Status = types.ReceiptStatusFailed + receipt.GasUsed = tx.Gas() + } + env.wo.Header().SetGasUsed(gasUsed) + env.txs = append(env.txs, tx) + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, gasUsed) + return receipt.Logs, true, nil + } + } if tx.ETXSender().Location().Equal(*tx.To().Location()) { // Quai->Qi conversion txGas := tx.Gas() var lockup *big.Int @@ -1166,8 +1381,7 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t } } if txGas < params.TxGas { - // No gas, the result is a no-op but the tx is still valid - return nil, false, nil + return nil, false, fmt.Errorf("insufficient gas for Qi->Quai conversion") } txGas -= params.TxGas if err := env.gasPool.SubGas(params.TxGas); err != nil { @@ -1176,7 +1390,8 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t gasUsed += params.TxGas denominations := misc.FindMinDenominations(tx.Value()) outputIndex := uint16(0) - + total := big.NewInt(0) + success := true // Iterate over the denominations in descending order for denomination := types.MaxDenomination; denomination >= 0; denomination-- { // If the denomination count is zero, skip it @@ -1186,6 +1401,7 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t for j := uint64(0); j < denominations[uint8(denomination)]; j++ { if txGas < params.CallValueTransferGas || outputIndex >= types.MaxOutputIndex { // No more gas, the rest of the denominations are lost but the tx is still valid + success = false break } txGas -= params.CallValueTransferGas @@ -1196,9 +1412,25 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t // the ETX hash is guaranteed to be unique utxoHash := types.UTXOHash(tx.Hash(), outputIndex, types.NewUtxoEntry(types.NewTxOut(uint8(denomination), tx.To().Bytes(), lock))) env.utxosCreate = append(env.utxosCreate, utxoHash) + total.Add(total, types.Denominations[uint8(denomination)]) outputIndex++ } } + receipt := &types.Receipt{Type: tx.Type(), Status: types.ReceiptStatusSuccessful, GasUsed: tx.Gas() - txGas, TxHash: tx.Hash(), + Logs: []*types.Log{{ + Address: *tx.To(), + Topics: []common.Hash{types.QuaiToQiConversionTopic}, + Data: total.Bytes(), + }}, + } + if !success { + receipt.Status = types.ReceiptStatusFailed + receipt.GasUsed = tx.Gas() + } + env.wo.Header().SetGasUsed(gasUsed) + env.txs = append(env.txs, tx) + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, gasUsed) + return receipt.Logs, true, nil } else { // This Qi ETX should cost more gas if err := env.gasPool.SubGas(params.CallValueTransferGas); err != nil { @@ -1207,23 +1439,41 @@ func (w *worker) commitTransaction(env *environment, parent *types.WorkObject, t utxoHash := types.UTXOHash(tx.OriginatingTxHash(), tx.ETXIndex(), types.NewUtxoEntry(types.NewTxOut(uint8(tx.Value().Uint64()), tx.To().Bytes(), common.Big0))) env.utxosCreate = append(env.utxosCreate, utxoHash) gasUsed += params.CallValueTransferGas + + env.wo.Header().SetGasUsed(gasUsed) + env.txs = append(env.txs, tx) + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, gasUsed) + return []*types.Log{}, false, nil } - env.wo.Header().SetGasUsed(gasUsed) - env.txs = append(env.txs, tx) - env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, gasUsed) - return []*types.Log{}, false, nil + } else if tx.Type() == types.ExternalTxType && types.IsConversionTx(tx) && tx.To().IsInQuaiLedgerScope() { // Qi->Quai Conversion gasUsed := env.wo.GasUsed() + params.QiToQuaiConversionGas env.wo.Header().SetGasUsed(gasUsed) env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, gasUsed) env.txs = append(env.txs, tx) return []*types.Log{}, false, nil // The conversion is locked and will be redeemed later + } else if tx.Type() == types.ExternalTxType && tx.EtxType() == types.WrappingQiType && tx.To().IsInQuaiLedgerScope() { // Qi wrapping ETX + if len(tx.Data()) != common.AddressLength { + return nil, false, fmt.Errorf("wrapping Qi ETX %x has invalid data length", tx.Hash()) + } + if tx.To() == nil { + return nil, false, fmt.Errorf("wrapping Qi ETX %x has no recipient", tx.Hash()) + } + ownerContractAddr := common.BytesToAddress(tx.Data(), w.chainConfig.Location) + if err := vm.WrapQi(env.state, ownerContractAddr, *tx.To(), common.OneInternal(w.chainConfig.Location), tx.Value(), w.chainConfig.Location); err != nil { + return nil, false, fmt.Errorf("could not wrap Qi: %w", err) + } + gasUsed := env.wo.GasUsed() + params.QiToQuaiConversionGas + env.wo.Header().SetGasUsed(gasUsed) + env.gasUsedAfterTransaction = append(env.gasUsedAfterTransaction, gasUsed) + env.txs = append(env.txs, tx) + return []*types.Log{}, false, nil } snap := env.state.Snapshot() // retrieve the gas used int and pass in the reference to the ApplyTransaction gasUsed := env.wo.GasUsed() stateUsed := env.wo.StateUsed() - receipt, quaiFees, err := ApplyTransaction(w.chainConfig, parent, *env.parentOrder, w.hc, &env.primaryCoinbase, env.gasPool, env.state, env.wo, tx, &gasUsed, &stateUsed, *w.hc.bc.processor.GetVMConfig(), &env.etxRLimit, &env.etxPLimit, w.logger) + receipt, quaiFees, err := ApplyTransaction(w.chainConfig, parent, *env.parentOrder, w.hc, &env.primaryCoinbase, env.gasPool, env.state, env.wo, tx, &gasUsed, &stateUsed, *w.hc.bc.processor.GetVMConfig(), &env.etxRLimit, &env.etxPLimit, env.batch, w.logger) if err != nil { w.logger.WithFields(log.Fields{ "err": err, @@ -1292,11 +1542,11 @@ func (w *worker) commitTransactions(env *environment, primeTerminus *types.WorkO } env.state.Prepare(etx.Hash(), env.tcount) - logs, receipt, err := w.commitTransaction(env, parent, etx) - if err == nil && receipt { + logs, hasLog, err := w.commitTransaction(env, parent, etx) + if err == nil && hasLog { coalescedLogs = append(coalescedLogs, logs...) env.tcount++ - } else if err == nil && !receipt { + } else if err == nil && !hasLog { env.tcount++ } else { w.logger.WithField("err", err).Error("Failed to commit an etx") @@ -1366,7 +1616,7 @@ func (w *worker) commitTransactions(env *environment, primeTerminus *types.WorkO // Start executing the transaction env.state.Prepare(tx.Hash(), env.tcount) - logs, receipt, err := w.commitTransaction(env, parent, tx) + logs, hasLog, err := w.commitTransaction(env, parent, tx) switch { case errors.Is(err, types.ErrGasLimitReached): // Pop the current out-of-gas transaction without shifting in the next from the account @@ -1396,7 +1646,7 @@ func (w *worker) commitTransactions(env *environment, primeTerminus *types.WorkO case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account - if receipt { + if hasLog { coalescedLogs = append(coalescedLogs, logs...) } env.tcount++ @@ -1481,6 +1731,10 @@ func (w *worker) prepareWork(genParams *generateParams, wo *types.WorkObject) (* num := parent.Number(nodeCtx) newWo := types.EmptyWorkObject(nodeCtx) newWo.WorkObjectHeader().SetLock(w.GetLockupByte()) + data := make([]byte, 0, 21) + data = append(data, w.GetLockupByte()) + data = append(data, defaultLockupContractAddress.Bytes()...) + newWo.WorkObjectHeader().SetData(data) newWo.SetParentHash(wo.Hash(), nodeCtx) if w.hc.IsGenesisHash(parent.Hash()) { newWo.SetNumber(big.NewInt(1), nodeCtx) @@ -1656,7 +1910,7 @@ func (w *worker) prepareWork(genParams *generateParams, wo *types.WorkObject) (* w.logger.WithField("err", err).Error("Failed to prepare header for sealing") return nil, err } - proposedWoHeader := types.NewWorkObjectHeader(newWo.Hash(), newWo.ParentHash(nodeCtx), newWo.Number(nodeCtx), newWo.Difficulty(), newWo.WorkObjectHeader().PrimeTerminusNumber(), newWo.TxHash(), newWo.Nonce(), newWo.Lock(), newWo.Time(), newWo.Location(), newWo.PrimaryCoinbase()) + proposedWoHeader := types.NewWorkObjectHeader(newWo.Hash(), newWo.ParentHash(nodeCtx), newWo.Number(nodeCtx), newWo.Difficulty(), newWo.WorkObjectHeader().PrimeTerminusNumber(), newWo.TxHash(), newWo.Nonce(), newWo.Lock(), newWo.Time(), newWo.Location(), newWo.PrimaryCoinbase(), newWo.Data()) proposedWoBody := types.NewWoBody(newWo.Header(), nil, nil, nil, nil, nil) proposedWo := types.NewWorkObject(proposedWoHeader, proposedWoBody, nil) env, err := w.makeEnv(parent, proposedWo, w.GetPrimaryCoinbase(), w.GetSecondaryCoinbase()) @@ -1664,8 +1918,12 @@ func (w *worker) prepareWork(genParams *generateParams, wo *types.WorkObject) (* w.logger.WithField("err", err).Error("Failed to create sealing context") return nil, err } - - if err, _ := RedeemLockedQuai(w.hc, proposedWo, parent, env.state); err != nil { + blockContext, err := NewEVMBlockContext(proposedWo, parent, w.hc, nil) + if err != nil { + return nil, err + } + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, env.state, w.chainConfig, *w.hc.bc.processor.GetVMConfig(), env.batch) + if _, err := RedeemLockedQuai(w.hc, proposedWo, parent, env.state, vmenv); err != nil { w.logger.WithField("err", err).Error("Failed to redeem locked Quai") return nil, err } @@ -1712,7 +1970,7 @@ func (w *worker) prepareWork(genParams *generateParams, wo *types.WorkObject) (* } return env, nil } else { - proposedWoHeader := types.NewWorkObjectHeader(newWo.Hash(), newWo.ParentHash(nodeCtx), newWo.Number(nodeCtx), newWo.Difficulty(), newWo.WorkObjectHeader().PrimeTerminusNumber(), types.EmptyRootHash, newWo.Nonce(), newWo.Lock(), newWo.Time(), newWo.Location(), newWo.PrimaryCoinbase()) + proposedWoHeader := types.NewWorkObjectHeader(newWo.Hash(), newWo.ParentHash(nodeCtx), newWo.Number(nodeCtx), newWo.Difficulty(), newWo.WorkObjectHeader().PrimeTerminusNumber(), types.EmptyRootHash, newWo.Nonce(), newWo.Lock(), newWo.Time(), newWo.Location(), newWo.PrimaryCoinbase(), newWo.Data()) proposedWoBody := types.NewWoBody(newWo.Header(), nil, nil, nil, nil, nil) proposedWo := types.NewWorkObject(proposedWoHeader, proposedWoBody, nil) return &environment{wo: proposedWo}, nil @@ -2026,6 +2284,9 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment, primeTermi if tx.ChainId().Cmp(w.chainConfig.ChainID) != 0 { return fmt.Errorf("tx %032x has wrong chain ID", tx.Hash()) } + if len(tx.Data()) != 0 && len(tx.Data()) != common.AddressLength { + return fmt.Errorf("tx %v emits UTXO with invalid data length %d", tx.Hash().Hex(), len(tx.Data())) + } gasUsed := env.wo.GasUsed() intrinsicGas := types.CalculateIntrinsicQiTxGas(tx, env.qiGasScalingFactor) gasUsed += intrinsicGas // the amount of block gas used in this transaction is only the txGas, regardless of ETXs emitted @@ -2072,6 +2333,7 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment, primeTermi totalConvertQitOut := big.NewInt(0) utxosCreateHashes := make([]common.Hash, 0, len(tx.TxOut())) conversion := false + wrapping := false var convertAddress common.Address outputs := make(map[uint]uint64) for txOutIdx, txOut := range tx.TxOut() { @@ -2096,7 +2358,7 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment, primeTermi } addresses[toAddr.Bytes20()] = struct{}{} - if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() { // Qi->Quai conversion + if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() && len(tx.Data()) == 0 { // Qi->Quai conversion if conversion && !toAddr.Equal(convertAddress) { // All convert outputs must have the same To address for aggregation return fmt.Errorf("tx %032x emits multiple convert UTXOs with different To addresses", tx.Hash()) } @@ -2109,6 +2371,16 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment, primeTermi outputs[uint(txOut.Denomination)] -= 1 // This output no longer exists because it has been aggregated delete(addresses, toAddr.Bytes20()) continue + } else if toAddr.Location().Equal(location) && toAddr.IsInQuaiLedgerScope() && len(tx.Data()) != 0 { // Wrapped Qi transaction + ownerContract := common.BytesToAddress(tx.Data(), location) + if _, err := ownerContract.InternalAndQuaiAddress(); err != nil { + return err + } + wrapping = true + convertAddress = toAddr + totalConvertQitOut.Add(totalConvertQitOut, types.Denominations[txOut.Denomination]) // Uses the same path as conversion but takes priority + outputs[uint(txOut.Denomination)] -= 1 // This output no longer exists because it has been aggregated + delete(addresses, toAddr.Bytes20()) } else if toAddr.IsInQuaiLedgerScope() { return fmt.Errorf("tx %032x emits UTXO with To address not in the Qi ledger scope", tx.Hash()) } @@ -2180,9 +2452,18 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment, primeTermi if txFeeInQuai.Cmp(minimumFeeInQuai) < 0 { return fmt.Errorf("tx %032x has insufficient fee for base fee * gas, have %d want %d", tx.Hash(), txFeeInQit.Uint64(), minimumFeeInQuai.Uint64()) } - if conversion { - if env.wo.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 && totalConvertQitOut.Cmp(types.Denominations[params.MinQiConversionDenomination]) < 0 { - return fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), totalConvertQitOut.Uint64()) + if conversion && env.wo.NumberU64(common.ZONE_CTX) >= params.GoldenAgeForkNumberV2 && totalConvertQitOut.Cmp(types.Denominations[params.MinQiConversionDenomination]) < 0 { + return fmt.Errorf("tx %032x emits convert UTXO with value %d less than minimum conversion denomination", tx.Hash(), totalConvertQitOut.Uint64()) + } + if conversion || wrapping { + if conversion && wrapping { + return fmt.Errorf("tx %032x emits both a conversion and a wrapping UTXO", tx.Hash()) + } + etxType := types.ConversionType + data := []byte{} + if wrapping { + etxType = types.WrappingQiType + data = tx.Data() } var etxInner types.ExternalTx if env.wo.NumberU64(common.ZONE_CTX) < params.GoldenAgeForkNumberV2 { @@ -2202,7 +2483,7 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment, primeTermi if ETXPCount > env.etxPLimit { return fmt.Errorf("tx [%v] emits too many cross-prime ETXs for block. emitted: %d, limit: %d", tx.Hash().Hex(), ETXPCount, env.etxPLimit) } - etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: types.ConversionType, OriginatingTxHash: tx.Hash(), Gas: remainingGas.Uint64()} // Value is in Qits not Denomination + etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: uint64(etxType), OriginatingTxHash: tx.Hash(), Gas: remainingGas.Uint64(), Data: data} // Value is in Qits not Denomination } else { // Since this transaction contains a conversion, check if the required conversion gas is paid // The user must pay this to the miner now, but it is only added to the block gas limit when the ETX is played in the destination @@ -2216,7 +2497,7 @@ func (w *worker) processQiTx(tx *types.Transaction, env *environment, primeTermi return fmt.Errorf("tx [%v] emits too many cross-prime ETXs for block. emitted: %d, limit: %d", tx.Hash().Hex(), ETXPCount, env.etxPLimit) } // Value is in Qits not Denomination - etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: types.ConversionType, OriginatingTxHash: tx.Hash(), Gas: 0} // Conversion gas is paid from the converted Quai balance (for new account creation, when redeemed) + etxInner = types.ExternalTx{Value: totalConvertQitOut, To: &convertAddress, Sender: common.ZeroAddress(location), EtxType: uint64(etxType), OriginatingTxHash: tx.Hash(), Gas: 0, Data: data} // Conversion gas is paid from the converted Quai balance (for new account creation, when redeemed) } gasUsed += params.ETXGas if err := env.gasPool.SubGas(params.ETXGas); err != nil { diff --git a/ethdb/batch.go b/ethdb/batch.go index 1353693318..933ef95cfd 100644 --- a/ethdb/batch.go +++ b/ethdb/batch.go @@ -36,6 +36,10 @@ type Batch interface { // Replay replays the batch contents. Replay(w KeyValueWriter) error + + SetPending(pending bool) + + GetPending(key []byte) (bool, []byte) } // Batcher wraps the NewBatch method of a backing data store. diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index 13d1e80c92..203e10f55c 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -411,16 +411,24 @@ func (db *Database) meter(refresh time.Duration) { // batch is a write-only leveldb batch that commits changes to its host database // when Write is called. A batch cannot be used concurrently. type batch struct { - db *leveldb.DB - b *leveldb.Batch - size int - logger *log.Logger + db *leveldb.DB + b *leveldb.Batch + size int + logger *log.Logger + setPending bool + pending map[string]*[]byte + pendingLock sync.RWMutex } // Put inserts the given value into the batch for later committing. func (b *batch) Put(key, value []byte) error { b.b.Put(key, value) b.size += len(value) + if b.setPending { + b.pendingLock.Lock() + b.pending[string(key)] = &value + b.pendingLock.Unlock() + } return nil } @@ -428,9 +436,32 @@ func (b *batch) Put(key, value []byte) error { func (b *batch) Delete(key []byte) error { b.b.Delete(key) b.size += len(key) + if b.setPending { + b.pendingLock.Lock() + b.pending[string(key)] = nil + b.pendingLock.Unlock() + } return nil } +func (b *batch) GetPending(key []byte) (bool, []byte) { + b.pendingLock.RLock() + defer b.pendingLock.RUnlock() + if val, ok := b.pending[string(key)]; ok { + if val == nil { + return true, nil + } + return false, *val + } + return false, nil +} + +// SetPending must be called for the batch to keep track of pending writes outside of leveldb. +func (b *batch) SetPending(val bool) { + b.pending = make(map[string]*[]byte) + b.setPending = val +} + // ValueSize retrieves the amount of data queued up for writing. func (b *batch) ValueSize() int { return b.size @@ -438,6 +469,8 @@ func (b *batch) ValueSize() int { // Write flushes any accumulated data to disk. func (b *batch) Write() error { + b.pending = nil + b.setPending = false return b.db.Write(b.b, nil) } @@ -445,6 +478,8 @@ func (b *batch) Write() error { func (b *batch) Reset() { b.b.Reset() b.size = 0 + b.pending = nil + b.setPending = false } // Replay replays the batch contents. diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index 0651009fc5..3e60d4dcad 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -277,6 +277,12 @@ func (b *batch) Logger() *log.Logger { return b.db.logger } +func (b *batch) SetPending(pending bool) {} + +func (b *batch) GetPending(key []byte) (bool, []byte) { + return false, nil +} + // iterator can walk over the (potentially partial) keyspace of a memory key // value store. Internally it is a deep copy of the entire iterated state, // sorted by keys. diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index 2b6b07a68b..1e2dea0989 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -475,6 +475,12 @@ func (b *batch) Logger() *log.Logger { return b.db.logger } +func (b *batch) SetPending(pending bool) {} + +func (b *batch) GetPending(key []byte) (bool, []byte) { + return false, nil +} + // pebbleIterator is a wrapper of underlying iterator in storage engine. // The purpose of this structure is to implement the missing APIs. type pebbleIterator struct { diff --git a/internal/quaiapi/quai_api.go b/internal/quaiapi/quai_api.go index dd0bc4f2d4..7e252500dd 100644 --- a/internal/quaiapi/quai_api.go +++ b/internal/quaiapi/quai_api.go @@ -30,6 +30,7 @@ import ( "github.com/dominant-strategies/go-quai/core" "github.com/dominant-strategies/go-quai/core/rawdb" "github.com/dominant-strategies/go-quai/core/types" + "github.com/dominant-strategies/go-quai/core/vm" "github.com/dominant-strategies/go-quai/crypto" "github.com/dominant-strategies/go-quai/log" "github.com/dominant-strategies/go-quai/metrics_config" @@ -271,6 +272,18 @@ func (s *PublicBlockChainQuaiAPI) GetLockupsByAddressAndRange(ctx context.Contex return jsonLockups, nil } +func (s *PublicBlockChainQuaiAPI) GetLockupsForContractAndMiner(ctx context.Context, ownerContract, beneficiaryMiner common.Address) (map[string]map[string][]interface{}, error) { + _, err := ownerContract.InternalAndQuaiAddress() + if err != nil { + return nil, err + } + _, err = beneficiaryMiner.InternalAddress() + if err != nil { + return nil, err + } + return vm.GetAllLockupData(s.b.Database(), ownerContract, beneficiaryMiner, s.b.NodeLocation(), s.b.Logger()) +} + func (s *PublicBlockChainQuaiAPI) GetOutPointsByAddressAndRange(ctx context.Context, address common.Address, start, end hexutil.Uint64) (map[string][]interface{}, error) { if start > end { return nil, fmt.Errorf("start is greater than end") diff --git a/params/denomination.go b/params/denomination.go index bcedd271e0..3a1f9fca23 100644 --- a/params/denomination.go +++ b/params/denomination.go @@ -16,6 +16,8 @@ package params +import "math/big" + // These are the multipliers for ether denominations. // Example: To get the wei value of an amount in 'gwei', use // @@ -25,3 +27,7 @@ const ( GWei = 1e9 Ether = 1e18 ) + +var ( + BigEther = new(big.Int).SetUint64(Ether) +) diff --git a/params/protocol_params.go b/params/protocol_params.go index 12e870d3e1..e6ff83c3e2 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -154,6 +154,7 @@ const ( ConversionConfirmationContext = common.PRIME_CTX // A conversion requires a single coincident Dom confirmation QiToQuaiConversionGas = 100000 // The gas used to convert Qi to Quai DefaultCoinbaseLockup = 0 // The default lockup byte for coinbase rewards + MaxCoinbaseTrancheElements = 100 // Maximum number of elements in a coinbase tranche ) var ( @@ -192,6 +193,8 @@ var ( MinBaseFeeInQits = big.NewInt(5) OneOverBaseFeeControllerAlpha = big.NewInt(100) BaseFeeMultiplier = big.NewInt(50) + + CoinbaseEpochBlocks uint64 = 100 // Maximum number of blocks in a coinbase tranche ) const ( diff --git a/quai/api_backend.go b/quai/api_backend.go index 18503a2e63..ee4ba81cd5 100644 --- a/quai/api_backend.go +++ b/quai/api_backend.go @@ -293,7 +293,7 @@ func (b *QuaiAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *st if err != nil { return nil, vmError, err } - return vm.NewEVM(context, txContext, state, b.quai.core.Config(), *vmConfig), vmError, nil + return vm.NewEVM(context, txContext, state, b.quai.core.Config(), *vmConfig, nil), vmError, nil } func (b *QuaiAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {