Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion relayer/cmd/chainlink-tron/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ in
];

# pin the vendor hash (update using 'pkgs.lib.fakeHash')
vendorHash = "sha256-SCl9rzV1LAN+0DTVDXCthin/VwR/rCNaz4WdxcpCPJY=";
vendorHash = "sha256-DgTypXNwCZR9e2yMVqh+FSouhf/cDtAUDVp8ttqZgMA=";

# postInstall script to write version and rev to share folder
postInstall = ''
Expand Down
10 changes: 5 additions & 5 deletions relayer/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.40.0
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc
google.golang.org/protobuf v1.36.10
)

require (
Expand Down Expand Up @@ -139,13 +140,12 @@ require (
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.12.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
google.golang.org/grpc v1.74.2 // indirect
google.golang.org/protobuf v1.36.7 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251007200510-49b9836ed3ff // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect
google.golang.org/grpc v1.76.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
)

// original author is not maintaining the repo anymore
replace github.com/fbsobreira/gotron-sdk => github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.4
replace github.com/fbsobreira/gotron-sdk => github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.5-0.20251014120029-d73d15cc23f7
22 changes: 12 additions & 10 deletions relayer/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,8 @@ github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.4 h1:hvqATtrZ0
github.com/smartcontractkit/chainlink-common/pkg/chipingress v0.0.4/go.mod h1:eKGyfTKzr0/PeR7qKN4l2FcW9p+HzyKUwAfGhm/5YZc=
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250911124514-5874cc6d62b2 h1:1/KdO5AbUr3CmpLjMPuJXPo2wHMbfB8mldKLsg7D4M8=
github.com/smartcontractkit/chainlink-protos/cre/go v0.0.0-20250911124514-5874cc6d62b2/go.mod h1:jUC52kZzEnWF9tddHh85zolKybmLpbQ1oNA4FjOHt1Q=
github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.4 h1:J4qtAo0ZmgX5pIr8Y5mdC+J2rj2e/6CTUC263t6mGOM=
github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.4/go.mod h1:4WhGgCA0smBbBud5mK+jnDb2wwndMvoqaWBJ3OV/7Bw=
github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.5-0.20251014120029-d73d15cc23f7 h1:qPGcryHOEipripujMtsip++fmVbJhyqO6eAGEq85r48=
github.com/smartcontractkit/chainlink-tron/relayer/gotron-sdk v0.0.5-0.20251014120029-d73d15cc23f7/go.mod h1:ea1LESxlSSOgc2zZBqf1RTkXTMthHaspdqUHd7W4lF0=
github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e h1:Hv9Mww35LrufCdM9wtS9yVi/rEWGI1UnjHbcKKU0nVY=
github.com/smartcontractkit/freeport v0.1.3-0.20250716200817-cb5dfd0e369e/go.mod h1:T4zH9R8R8lVWKfU7tUvYz2o2jMv1OpGCdpY2j2QZXzU=
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs=
Expand Down Expand Up @@ -683,23 +683,25 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20210401141331-865547bb08e2/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/genproto/googleapis/api v0.0.0-20251007200510-49b9836ed3ff h1:8Zg5TdmcbU8A7CXGjGXF1Slqu/nIFCRaR3S5gT2plIA=
google.golang.org/genproto/googleapis/api v0.0.0-20251007200510-49b9836ed3ff/go.mod h1:dbWfpVPvW/RqafStmRWBUpMN14puDezDMHxNYiRfQu0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
Expand All @@ -711,8 +713,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
Expand Down
129 changes: 129 additions & 0 deletions relayer/txm/seralizer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package txm

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"time"

"github.com/fbsobreira/gotron-sdk/pkg/abi"
"github.com/fbsobreira/gotron-sdk/pkg/address"
"github.com/fbsobreira/gotron-sdk/pkg/http/common"
"github.com/fbsobreira/gotron-sdk/pkg/proto/core"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
)

type Serializer struct {
TransactionType core.Transaction_Contract_ContractType
FromAddress address.Address
ContractAddress address.Address
Method string
Params []any
CallValueSun int64
FeeLimitSun int64
RefBlockBytes []byte
RefBlockHash []byte
ExpirationMillis int64
TimestampMillis int64
}

const DefaultExpirationMillis = 30_000 // 30 seconds

func (p *Serializer) BuildTransaction() (*common.Transaction, error) {
if p.TransactionType != core.Transaction_Contract_TriggerSmartContract {
return nil, fmt.Errorf("invalid transaction type: %d", p.TransactionType)
}

if len(p.RefBlockBytes) != 2 && len(p.RefBlockHash) != 8 {
return nil, fmt.Errorf("invalid ref block bytes or hash")
}

callData, err := p.buildCallData()
if err != nil {
return nil, fmt.Errorf("failed to build call data: %+w", err)
}

smartContractCall := &core.TriggerSmartContract{
OwnerAddress: p.FromAddress.Bytes(),
ContractAddress: p.ContractAddress.Bytes(),
Data: callData,
CallValue: p.CallValueSun,
}

callPayload, err := anypb.New(smartContractCall)
if err != nil {
return nil, fmt.Errorf("failed to create call payload: %+w", err)
}

contract := &core.Transaction_Contract{
Parameter: callPayload,
Type: core.Transaction_Contract_TriggerSmartContract,
}

now := time.Now().UnixMilli()
timestamp := p.TimestampMillis
if timestamp == 0 {
timestamp = now
}

expiration := p.ExpirationMillis
if expiration == 0 {
expiration = now + DefaultExpirationMillis
}

rawData := &core.TransactionRaw{
Contract: []*core.Transaction_Contract{contract},
FeeLimit: p.FeeLimitSun,
Expiration: expiration,
Timestamp: timestamp,
RefBlockBytes: p.RefBlockBytes,
RefBlockHash: p.RefBlockHash,
}

rawBytes, err := proto.Marshal(rawData)
if err != nil {
return nil, fmt.Errorf("failed to marshal raw data: %+w", err)
}

hash := sha256.Sum256(rawBytes)
txIDHex := hex.EncodeToString(hash[:])
rawDataHex := hex.EncodeToString(rawBytes)

commonRawData := common.RawData{
Contract: []common.Contract{
{
Parameter: common.Parameter{
Value: common.ParameterValue{
OwnerAddress: p.FromAddress.String(),
ContractAddress: p.ContractAddress.String(),
Data: hex.EncodeToString(smartContractCall.Data),
Amount: p.CallValueSun,
},
TypeUrl: "type.googleapis.com/protocol.TriggerSmartContract",
},
Type: "TriggerSmartContract",
},
},
RefBlockBytes: hex.EncodeToString(p.RefBlockBytes),
RefBlockHash: hex.EncodeToString(p.RefBlockHash),
Expiration: expiration,
FeeLimit: p.FeeLimitSun,
Timestamp: timestamp,
}

return &common.Transaction{
Visible: true,
TxID: txIDHex,
RawData: commonRawData,
RawDataHex: rawDataHex,
}, nil
}

func (p *Serializer) buildCallData() ([]byte, error) {
parsed, err := abi.Pack(p.Method, p.Params)
if err != nil {
return nil, fmt.Errorf("failed to pack params: %+w", err)
}
return parsed, nil
}
75 changes: 70 additions & 5 deletions relayer/txm/txm.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/fbsobreira/gotron-sdk/pkg/http/common"
"github.com/fbsobreira/gotron-sdk/pkg/http/fullnode"
"github.com/fbsobreira/gotron-sdk/pkg/http/soliditynode"
"github.com/fbsobreira/gotron-sdk/pkg/proto/core"
"github.com/google/uuid"

"github.com/smartcontractkit/chainlink-common/pkg/logger"
Expand Down Expand Up @@ -171,14 +172,37 @@ func (t *TronTxm) broadcastLoop() {
for {
select {
case tx := <-t.BroadcastChan:
triggerResponse, err := t.TriggerSmartContract(ctx, tx)
feeLimit, err := t.calculateFeeLimit(tx)
if err != nil {
// TODO: is it ok to leave this transaction unmarked as fatal?
t.Logger.Errorw("failed to trigger smart contract", "error", err, "tx", tx, "txID", tx.ID)
t.Logger.Errorw("failed to calculate fee limit", "error", err, "txID", tx.ID)
continue
}

// Get the latest block info
refBlockBytes, refBlockHash, err := t.computeRefBlockBytesAndHash()
if err != nil {
t.Logger.Errorw("failed to compute ref block bytes and hash", "error", err, "txID", tx.ID)
continue
}

txSerializer := Serializer{
TransactionType: core.Transaction_Contract_TriggerSmartContract,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be implicit and we don't need the core import in this file or the != TriggerSmartContract check in serializer.go

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, aim here was to ensure that if this changes in the future to catch this

FromAddress: tx.FromAddress,
ContractAddress: tx.ContractAddress,
Method: tx.Method,
Params: tx.Params,
CallValueSun: 0,
FeeLimitSun: int64(feeLimit),
RefBlockBytes: refBlockBytes,
RefBlockHash: refBlockHash,
}

coreTx, err := txSerializer.BuildTransaction()
if err != nil {
t.Logger.Errorw("failed to build transaction", "error", err, "txID", tx.ID)
continue
}

coreTx := triggerResponse.Transaction
txHash := coreTx.TxID

// RefBlockNum is optional and does not seem in use anymore.
Expand All @@ -187,7 +211,7 @@ func (t *TronTxm) broadcastLoop() {

_, err = t.SignAndBroadcast(ctx, tx.FromAddress, coreTx)
if err != nil {
t.Logger.Errorw("transaction failed to broadcast", "txHash", txHash, "error", err, "tx", tx, "triggerResponse", triggerResponse, "txID", tx.ID)
t.Logger.Errorw("transaction failed to broadcast", "txHash", txHash, "error", err, "tx", tx, "coreTx", coreTx, "txID", tx.ID)
txStore.OnFatalError(tx.ID)
continue
}
Expand All @@ -202,6 +226,47 @@ func (t *TronTxm) broadcastLoop() {
}
}

func (t *TronTxm) computeRefBlockBytesAndHash() ([]byte, []byte, error) {
nowBlock, err := t.GetClient().GetNowBlockFullNode()
if err != nil {
return nil, nil, fmt.Errorf("failed to get now block: %+w", err)
}

blockNumber := nowBlock.BlockHeader.RawData.Number
blockId, err := hex.DecodeString(nowBlock.BlockID)
if err != nil {
return nil, nil, fmt.Errorf("failed to decode block id: %+w", err)
}

refBlockBytes := []byte{byte((blockNumber >> 8) & 0xFF), byte(blockNumber & 0xFF)} // last 2 bytes
refBlockHash := blockId[8:16]
return refBlockBytes, refBlockHash, nil
}

func (t *TronTxm) calculateFeeLimit(tx *TronTx) (int32, error) {
energyUsed, err := t.estimateEnergy(tx)
if err != nil {
return 0, fmt.Errorf("failed to estimate energy: %+w", err)
}

energyUnitPrice := DEFAULT_ENERGY_UNIT_PRICE

if energyPrices, err := t.GetClient().GetEnergyPrices(); err == nil {
if parsedPrice, err := ParseLatestEnergyPrice(energyPrices.Prices); err == nil {
energyUnitPrice = parsedPrice
} else {
t.Logger.Errorw("error parsing energy unit price", "error", err, "txID", tx.ID)
}
} else {
t.Logger.Errorw("failed to get energy unit price", "error", err, "txID", tx.ID)
}

feeLimit := energyUnitPrice * int32(energyUsed)
paddedFeeLimit := CalculatePaddedFeeLimit(feeLimit, tx.EnergyBumpTimes, t.Config.EnergyMultiplier)

return paddedFeeLimit, nil
}

func (t *TronTxm) TriggerSmartContract(ctx context.Context, tx *TronTx) (*fullnode.TriggerSmartContractResponse, error) {
energyUsed, err := t.estimateEnergy(tx)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions relayer/txm/txm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func createDefaultMockClient(t *testing.T) *mocks.CombinedClient {
txid, _ := hex.DecodeString("2a037789237971c1c1d648f7b90b70c68a9aa6b0a2892f947213286346d0210d")

combinedClient.On("GetNowBlockFullNode").Maybe().Return(&soliditynode.Block{
BlockID: "000000000325a7105234af0154beb7fcb0363b809cb469fe7e0e0fd571bbd054",
BlockHeader: &soliditynode.BlockHeader{
RawData: &soliditynode.BlockHeaderRaw{
Timestamp: 1000,
Expand Down
Loading