Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up, add tests, verify staging/prod upgrade #81

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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 go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ toolchain go1.22.5
require (
github.com/hashicorp/go-plugin v1.6.2-0.20240829161738-06afb6d7ae99
github.com/shopspring/decimal v1.4.0
github.com/smartcontractkit/chainlink-common v0.3.0
github.com/smartcontractkit/chainlink-common v0.3.1-0.20241010121129-90d73545782e
github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/smartcontractkit/chainlink-common v0.3.0 h1:mUXHBzzw2qPKyw6gPAC8JhO+ryT8maY+rBi9NFtqEy0=
github.com/smartcontractkit/chainlink-common v0.3.0/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko=
github.com/smartcontractkit/chainlink-common v0.3.1-0.20241010121129-90d73545782e h1:6D1uTPgyTAaXGeO7Owu96cuOzQB8xnCMkjFKrjtHhY0=
github.com/smartcontractkit/chainlink-common v0.3.1-0.20241010121129-90d73545782e/go.mod h1:tsGgeEJc5SUSlfVGSX0wR0EkRU3pM58D6SKF97V68ko=
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7 h1:12ijqMM9tvYVEm+nR826WsrNi6zCKpwBhuApq127wHs=
github.com/smartcontractkit/grpc-proxy v0.0.0-20240830132753-a7e17fec5ab7/go.mod h1:FX7/bVdoep147QQhsOPkYsPEXhGZjeYx6lBSaSXtZOA=
github.com/smartcontractkit/libocr v0.0.0-20241007185508-adbe57025f12 h1:NzZGjaqez21I3DU7objl3xExTH4fxYvzTqar8DC6360=
Expand Down
2 changes: 1 addition & 1 deletion llo/channel_definitions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func Test_VerifyChannelDefinitions(t *testing.T) {
channelDefs[i] = llotypes.ChannelDefinition{}
}
err := VerifyChannelDefinitions(channelDefs)
assert.EqualError(t, err, "too many channels, got: 10001/10000")
assert.EqualError(t, err, "too many channels, got: 2001/2000")
})

t.Run("fails for channel with no streams", func(t *testing.T) {
Expand Down
60 changes: 60 additions & 0 deletions llo/json_report_codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"

"github.com/smartcontractkit/libocr/offchainreporting2/types"
ocr2types "github.com/smartcontractkit/libocr/offchainreporting2/types"

llotypes "github.com/smartcontractkit/chainlink-common/pkg/types/llo"
)
Expand Down Expand Up @@ -110,6 +111,10 @@ func (cdc JSONReportCodec) Decode(b []byte) (r Report, err error) {
return r, fmt.Errorf("failed to decode StreamValue: %w", err)
}
}
if d.SeqNr == 0 {
// catch obviously bad inputs, since a valid report can never have SeqNr == 0
return r, fmt.Errorf("missing SeqNr")
}

return Report{
ConfigDigest: cd,
Expand All @@ -121,3 +126,58 @@ func (cdc JSONReportCodec) Decode(b []byte) (r Report, err error) {
Specimen: d.Specimen,
}, err
}

// TODO: Needs tests, MERC-3524
func (cdc JSONReportCodec) Pack(digest types.ConfigDigest, seqNr uint64, report ocr2types.Report, sigs []types.AttributedOnchainSignature) ([]byte, error) {
type packed struct {
ConfigDigest types.ConfigDigest `json:"configDigest"`
SeqNr uint64 `json:"seqNr"`
Report json.RawMessage `json:"report"`
Sigs []types.AttributedOnchainSignature `json:"sigs"`
}
p := packed{
ConfigDigest: digest,
SeqNr: seqNr,
Report: json.RawMessage(report), // TODO: check if its valid JSON
Sigs: sigs,
}
return json.Marshal(p)
}

// TODO: Needs tests, MERC-3524
func (cdc JSONReportCodec) Unpack(b []byte) (digest types.ConfigDigest, seqNr uint64, report ocr2types.Report, sigs []types.AttributedOnchainSignature, err error) {
type packed struct {
ConfigDigest string `json:"configDigest"`
SeqNr uint64 `json:"seqNr"`
Report json.RawMessage `json:"report"`
Sigs []types.AttributedOnchainSignature `json:"sigs"`
}
p := packed{}
err = json.Unmarshal(b, &p)
if err != nil {
return digest, seqNr, report, sigs, fmt.Errorf("failed to unpack report: expected JSON (got: %s); %w", b, err)
}
cdBytes, err := hex.DecodeString(p.ConfigDigest)
if err != nil {
return digest, seqNr, report, sigs, fmt.Errorf("invalid ConfigDigest; %w", err)
}
cd, err := types.BytesToConfigDigest(cdBytes)
if err != nil {
return digest, seqNr, report, sigs, fmt.Errorf("invalid ConfigDigest; %w", err)
}
return cd, p.SeqNr, ocr2types.Report(p.Report), p.Sigs, nil
}

// TODO: Needs tests, MERC-3524
func (cdc JSONReportCodec) UnpackDecode(b []byte) (digest types.ConfigDigest, seqNr uint64, report Report, sigs []types.AttributedOnchainSignature, err error) {
var encodedReport []byte
digest, seqNr, encodedReport, sigs, err = cdc.Unpack(b)
if err != nil {
return digest, seqNr, report, sigs, err
}
r, err := cdc.Decode(encodedReport)
if err != nil {
return digest, seqNr, report, sigs, err
}
return digest, seqNr, r, sigs, nil
}
7 changes: 7 additions & 0 deletions llo/json_report_codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,11 @@ func Test_JSONCodec(t *testing.T) {

assert.Equal(t, r, decoded)
})
t.Run("invalid input fails decode", func(t *testing.T) {
cdc := JSONReportCodec{}
_, err := cdc.Decode([]byte(`{}`))
assert.EqualError(t, err, "invalid ConfigDigest; cannot convert bytes to ConfigDigest. bytes have wrong length 0")
_, err = cdc.Decode([]byte(`{"ConfigDigest":"0102030000000000000000000000000000000000000000000000000000000000"}`))
assert.EqualError(t, err, "missing SeqNr")
})
}
19 changes: 3 additions & 16 deletions llo/llo_offchain_config.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions llo/llo_offchain_config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,4 @@ syntax="proto3";
package v1;
option go_package = ".;llo";

message LLOOffchainConfigProto {
bytes predecessorConfigDigest = 1;
}
message LLOOffchainConfigProto {}
8 changes: 0 additions & 8 deletions llo/must.go

This file was deleted.

28 changes: 1 addition & 27 deletions llo/offchain_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,11 @@ package llo
import (
"fmt"

"github.com/smartcontractkit/libocr/offchainreporting2/types"
"google.golang.org/protobuf/proto"
)

type OffchainConfig struct {
// We use the offchainconfig of the plugin to tell the plugin the
// configdigest of its predecessor protocol instance.
//
// NOTE: Set here:
// https://github.com/smartcontractkit/mercury-v1-sketch/blob/f52c0f823788f86c1aeaa9ba1eee32a85b981535/onchain/src/ConfigurationStore.sol#L13
// TODO: This needs to be implemented alongside staging/production
// switchover support: https://smartcontract-it.atlassian.net/browse/MERC-3386
PredecessorConfigDigest *types.ConfigDigest
// TODO: Billing
// https://smartcontract-it.atlassian.net/browse/MERC-1189
// QUESTION: Previously we stored ExpiryWindow and BaseUSDFeeCents in offchain
// config, but those might be channel specific so need to move to
// channel definition
// ExpirationWindow uint32 `json:"expirationWindow"` // Integer number of seconds
// BaseUSDFee decimal.Decimal `json:"baseUSDFee"` // Base USD fee
// NOTE: Currently OffchainConfig does not contain anything, and is not used
}

func DecodeOffchainConfig(b []byte) (o OffchainConfig, err error) {
Expand All @@ -31,21 +16,10 @@ func DecodeOffchainConfig(b []byte) (o OffchainConfig, err error) {
if err != nil {
return o, fmt.Errorf("failed to decode offchain config: expected protobuf (got: 0x%x); %w", b, err)
}
if len(pbuf.PredecessorConfigDigest) > 0 {
var predecessorConfigDigest types.ConfigDigest
predecessorConfigDigest, err = types.BytesToConfigDigest(pbuf.PredecessorConfigDigest)
if err != nil {
return o, err
}
o.PredecessorConfigDigest = &predecessorConfigDigest
}
return
}

func (c OffchainConfig) Encode() ([]byte, error) {
pbuf := LLOOffchainConfigProto{}
if c.PredecessorConfigDigest != nil {
pbuf.PredecessorConfigDigest = c.PredecessorConfigDigest[:]
}
return proto.Marshal(&pbuf)
}
26 changes: 1 addition & 25 deletions llo/offchain_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,13 @@ package llo
import (
"testing"

"github.com/smartcontractkit/libocr/offchainreporting2/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_OffchainConfig(t *testing.T) {
t.Run("garbage bytes", func(t *testing.T) {
_, err := DecodeOffchainConfig([]byte{1})
require.Error(t, err)

assert.Contains(t, err.Error(), "failed to decode offchain config: expected protobuf (got: 0x01); proto:")
})

t.Run("zero length for PredecessorConfigDigest is ok", func(t *testing.T) {
decoded, err := DecodeOffchainConfig([]byte{})
require.NoError(t, err)
assert.Equal(t, OffchainConfig{}, decoded)
})

t.Run("encoding nil PredecessorConfigDigest is ok", func(t *testing.T) {
cfg := OffchainConfig{nil}

b, err := cfg.Encode()
require.NoError(t, err)

assert.Len(t, b, 0)
})

t.Run("encode and decode", func(t *testing.T) {
cd := types.ConfigDigest([32]byte{1, 2, 3})
cfg := OffchainConfig{&cd}
cfg := OffchainConfig{}

b, err := cfg.Encode()
require.NoError(t, err)
Expand Down
80 changes: 80 additions & 0 deletions llo/onchain_config_codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package llo

import (
"fmt"
"math/big"

"github.com/smartcontractkit/libocr/bigbigendian"
"github.com/smartcontractkit/libocr/offchainreporting2/types"
)

const onchainConfigVersion = 1

var onchainConfigVersionBig = big.NewInt(onchainConfigVersion)

const onchainConfigEncodedLength = 2 * 32 // 2x 32bit evm word: version and predecessorConfigDigest

type OnchainConfig struct {
Version uint8
PredecessorConfigDigest *types.ConfigDigest
}

type OnchainConfigCodec interface {
Decode(b []byte) (OnchainConfig, error)
Encode(OnchainConfig) ([]byte, error)
}

var _ OnchainConfigCodec = EVMOnchainConfigCodec{}

// EVMOnchainConfigCodec provides a llo-specific implementation of
// OnchainConfigCodec.
//
// An encoded onchain config is expected to be in the format
// <version><predecessorConfigDigest>
// where version is a uint8 and min and max are in the format
// returned by EncodeValueInt192.
type EVMOnchainConfigCodec struct{}

// TODO: Needs fuzz testing - MERC-3524
func (EVMOnchainConfigCodec) Decode(b []byte) (OnchainConfig, error) {
if len(b) != onchainConfigEncodedLength {
return OnchainConfig{}, fmt.Errorf("unexpected length of OnchainConfig, expected %v, got %v", onchainConfigEncodedLength, len(b))
}

v, err := bigbigendian.DeserializeSigned(32, b[:32])
if err != nil {
return OnchainConfig{}, err
}
if v.Cmp(onchainConfigVersionBig) != 0 {
return OnchainConfig{}, fmt.Errorf("unexpected version of OnchainConfig, expected %v, got %v", onchainConfigVersion, v)
}

o := OnchainConfig{
Version: uint8(v.Uint64()),
}

cd := types.ConfigDigest(b[32:64])
if (cd != types.ConfigDigest{}) {
o.PredecessorConfigDigest = &cd
}
return o, nil
}

// TODO: Needs fuzz testing - MERC-3524
func (EVMOnchainConfigCodec) Encode(c OnchainConfig) ([]byte, error) {
if c.Version != onchainConfigVersion {
return nil, fmt.Errorf("unexpected version of OnchainConfig, expected %v, got %v", onchainConfigVersion, c.Version)
}
verBytes, err := bigbigendian.SerializeSigned(32, onchainConfigVersionBig)
if err != nil {
return nil, err
}
cdBytes := make([]byte, 32)
if c.PredecessorConfigDigest != nil {
copy(cdBytes, c.PredecessorConfigDigest[:])
}
result := make([]byte, 0, onchainConfigEncodedLength)
result = append(result, verBytes...)
result = append(result, cdBytes...)
return result, nil
}
Loading
Loading